Обещания JavaScript: выполнение обещаний последовательно

#javascript #es6-promise

#javascript #es6-обещание

Вопрос:

В попытке более четко понять обещания, я прочитал несколько очень интересных статей на эту тему. Я наткнулся на следующий код, который идеально подходит для последовательного выполнения обещаний. Но я не в состоянии понять, как это работает.

  function doFirstThing(){
   return new Promise(function(resolve,reject){
       setTimeout(()=>{
           resolve(1);
       },1000)
   })
 }

 function doSecondThing(res){
   return new Promise(function(resolve,reject){
       setTimeout(()=>{
           resolve(res   1);
       },1000)
   })
 }

 function doThirdThing(res){
   return new Promise(function(resolve,reject){
       setTimeout(()=>{
           resolve(res   2);
       },1000)
   })
 }
 promiseFactories = [doFirstThing, doSecondThing, doThirdThing];

 function executeSequentially(promiseFactories) {
         var result = Promise.resolve(); // this is the most problematic line 
               promiseFactories.forEach(function (promiseFactory) {
               result = result.then(promiseFactory);// what is happening here ?
    });
    return resu<
 }

 executeSequentially(promiseFactories)
  

Я понимаю, что обещания выполняются сразу после их создания. По какой-то причине я не могу понять процесс выполнения. Особенно эта следующая строка:

 var result = Promise.resolve()//and empty promise is created.
  

Пожалуйста, если кто-нибудь может помочь мне понять, как вызов метода promiseFactory внутри метода ‘then’ пустого обещания заставляет его выполняться последовательно, вот так. Или это из-за цикла forEach?

 result = result.then(promiseFactory);
  

Я попытался заменить ‘forEach’ функцией ‘map’ и все равно получил тот же результат. т.е. методы, в которых выполнялись последовательно.
Кроме того, как передается значение от одной связанной функции к другой?

Любая помощь или статья / блог высоко ценятся.

Ответ №1:

Вы можете представить обещание в виде окна с выполнением внутри. Как только обещание создано, начинается выполнение. Чтобы получить результирующее значение, вы должны открыть окно. Вы можете использовать then для этого:

 Promise.resolve(5).then(result => console.log(result)); // prints 5
  

Если вы хотите связать обещания в цепочку, вы можете сделать это, открывая поле одно за другим:

 Promise.resolve(5)
  .then(result => Promise.resolve(result   1))
  .then(result => Promise.resolve(result * 2))
  .then(result => console.log(result));  // prints 12
  

Эта цепочка делает выполнение синхронным (одно за другим).

Если вы хотите выполнить несколько обещаний асинхронно (вы не объединяете результаты в цепочку), вы можете использовать Promise.all :

 Promise.all([Promise.resolve(1), Promise.resolve(2), Promise.resolve(3)])
  .then(result => console.log(result));  // prints [1,2,3]
  

В вашем случае:

 Promise.all(promiseFactories).then(result => console.log(result));
  

Другой вариант работы с обещаниями заключается в await их:

 (async ()=> {
   var res1 = await Promise.resolve(5);
   var res2 = await Promise.resolve(res1   1);
   var res3 = await Promise.resolve(res2 * 2);
   console.log(res3); // prints 12
})();
  

await работает аналогично then — превращает асинхронное выполнение в синхронное.

В вашем случае:

 async function executeSequentially(promiseFactories) {
    for (const p of promiseFactories) {
        const result = await p;
        console.log(result);
    } 
}
  

Примечание: await упаковывает значение в обещание из коробки:

 var res1 = await 5; // same as await Promise.resolve(5)
  

Комментарии:

1. спасибо за ответ. Итак, безопасно ли понимать, что выполнение обещаний внутри цикла с использованием await гарантирует, что они выполняются последовательно?

2. Да, определенно. await разрешает обещания синхронно.

3. Кроме того,@ttulka согласны ли вы, что promise.all() всегда выполняется параллельно? Кстати, спасибо за четкое и простое объяснение 🙂

4. Да, Promise.all ожидает выполнения всех обещаний асинхронно, а затем возвращает разрешенные результаты в массиве, упорядоченном в том же порядке, что и заданный в качестве параметра.

Ответ №2:

executeSequentially Метод возвращает все обещания одно за другим. Это происходит с повторением promiseFactory , но это может быть записано как:

 function executeSequentially(promiseFactories) {
  return doFirstThing()
  .then(() => doSecondThing())
  .then(doThirdThing() );
}
  

Это то же самое. По сути, мы возвращаем обещание.

Однако теперь мы хотим повторить набор обещаний.

При повторении нам нужно присоединить текущее обещание к предыдущему с помощью then . Но forEach не предоставляет следующее обещание — или предыдущее — на каждой итерации. И все же нам все еще это нужно, чтобы продолжать связывать обещания одно за другим. Следовательно, result «взлом»:

 function executeSequentially(promiseFactories) {
  var result = Promise.resolve(); /*We need a thing that keeps yelling 
  the previous promise in every iteration, so we can keep chaining.
  This 'result' var is that thing. This is keeping a Promise in every
  iteration that resolves when all the previous promises resolve
  sequentially. Since we don't have a Promise in the array
  previous to the first one, we fabricate one out of 'thin air'
  with Promise.resolve() */
  promiseFactories.forEach(function (promiseFactory) {
    result = result.then(promiseFactory); /* Here result is update
    with a new Promise, with is the result of  chaining `result`
    with the current one. Since `result` already had all the previous ones,
    at the end, `result` will be a Promise that depends upon all the
    Promises resolution.*/
  });
return resu<
}
  

Теперь, есть также синтаксическая особенность, которая, возможно, озадачивает вас:

 result = result.then(promiseFactory);
  

Эта строка почти такая же, как следующая:

 result = result.then(resolvedValue => promiseFactory(resolvedValue));
  

Пожалуйста, если кто-нибудь может помочь мне понять, как вызов метода promiseFactory внутри метода ‘then’ пустого обещания заставляет его выполняться последовательно, вот так. Или это из-за цикла forEach?

Во-первых, promiseFactory там довольно плохое название. Метод лучше записать следующим образом:

 function executeSequentially(promises) {
  var result = Promise.resolve(); // this is the most problematic line 
        promises.forEach(function (currentPromise) {
        result = result.then(currentPromise);// what is happening here ?
});
return resu<
}
  

Итак:

как вызов currentPromise метода внутри метода ‘then’ пустого обещания заставляет его выполняться последовательно?

Это заставляет выполнять последовательно, потому что, когда вы присоединяете обещание к другому с помощью then , оно выполняется последовательно. Это then вещь, это совсем не связано с тем фактом, что мы повторяем обещания. С простыми обещаниями вне итерации это работает практически так же:

 Promise.resolve() // fake Promises that resolves instanly
.then(fetchUsersFromDatabase) // a function that returns a Promise and takes
// like 1 second. It won't be called until the first one resolves
.then(processUsersData) // another function that takes input from the first, and
// do a lot of complex and asynchronous computations with data from the previous promise.
// it won't be called until `fetchUsersFromDatabase()` resolves, that's what
// `then()` does.
.then(sendDataToClient); // another function that will never be called until
// `processUsersData()` resolves
  

Комментарии:

1. Спасибо за ответ. Теперь я четко понимаю фрагмент кода.

Ответ №3:

Всегда рекомендуется использовать Promise.all , если вы хотите такого поведения:

 function doFirstThing() {
      return new Promise(function(resolve, reject) {
        setTimeout(() => {
          resolve(1);
        }, 1000)
      })
    }
    
    function doSecondThing(res) {
      return new Promise(function(resolve, reject) {
        setTimeout(() => {
          resolve(res   1);
        }, 1000)
      })
    }
    
    function doThirdThing(res) {
      return new Promise(function(resolve, reject) {
        setTimeout(() => {
          resolve(res   2);
        }, 1000)
      })
    }
    let promiseFactories = [doFirstThing(2), doSecondThing(1), doThirdThing(3)];
    
    Promise.all(promiseFactories)
      .then(data => {
        console.log("completed all promises", data);
      })  

Запускать их последовательно одно за другим:

 function doFirstThing() {
  return new Promise(function(resolve, reject) {
    setTimeout(() => {
      resolve(1);
    }, 1000)
  })
}

function doSecondThing(res) {
  return new Promise(function(resolve, reject) {
    setTimeout(() => {
      resolve(res   1);
    }, 3000)
  })
}

function doThirdThing(res) {
  return new Promise(function(resolve, reject) {
    setTimeout(() => {
      resolve(res   2);
    }, 5000)
  })
}
promiseFactories = [doFirstThing, doSecondThing, doThirdThing];

function executeSequentially(promiseFactories) {
  promiseFactories.forEach(function(promiseFactory) {
    promiseFactory(1).then((data) =>  {
    console.log(data)
    });
  });
}

executeSequentially(promiseFactories);  

Комментарии:

1. Promise.all() запускает методы параллельно, а не последовательно.

2. @NidhinRaj добавил код для этого. Проверьте обновленный ответ

3. Спасибо. Я ищу объяснение того, как это выполняется последовательно. И почему var result = Promise.resolve(); находится вне цикла. Почему Promise.resolve().then(promiseFactory) в стороне цикла не работает? Кроме того, попробуйте установить значение тайм-аута равным 1000 для всех фабрик. Вы обнаружите, что все обещания выполняются параллельно.

4. @NidhinRaj нам не нужно var result = Promise.resolve();

5. @NidhinRaj если тайм-аут составит 1 секунду, то он инициализирует эти тайм-ауты равными 1 секунде, и все обещания будут выполнены одновременно за 1 секунду. Итак, чтобы они выглядели последовательно, я изменил значение тайм-аута

Ответ №4:

Если мы разместим цикл foreach, это будет выглядеть следующим образом

 function doFirstThing(){
   return new Promise(function(resolve,reject){
       setTimeout(()=>{
       console.log(1);
           resolve(1);
       },1000)
   })
 }

 function doSecondThing(res){
   return new Promise(function(resolve,reject){
       setTimeout(()=>{
       console.log(2);
           resolve(res   1);
       },2000)
   })
 }

 function doThirdThing(res){
   return new Promise(function(resolve,reject){
       setTimeout(()=>{
       console.log(3);
           resolve(res   2);
       },3000)
   })
 }


Promise.resolve()
       .then(doFirstThing())
       .then(doSecondThing())
       .then(doThirdThing());  

Я понимаю, что обещания выполняются сразу после их создания. По какой-то причине я не могу понять процесс выполнения. Особенно эта следующая строка: var result = Promise.resolve()//and empty promise is created.

Это просто для того, чтобы получить доступ к начальной точке цепочки обещаний. Здесь это уже решенное обещание. Чтобы лучше понять это, вы можете использовать одно из своих обещаний, чтобы получить доступ к цепочке обещаний, как показано ниже.

 let promiseFactories= [doSecondThing, doThirdThing];

let result = doFirstThing();

promiseFactories.forEach(function (promiseFactory) {
     result = result.then(promiseFactory);
});
  

Это тоже сработает.

Комментарии:

1. Спасибо за ответ. Это был момент, который я пытался понять. Что касается вашего кода, если мы установим для всего таймаута значение 1000, кажется, что все обещания выполняются одновременно. Они могут выполняться в последовательном порядке, только если они находятся в цикле. Я прав?

2. Правильно, потому что цикл создает динамическую цепочку обещаний, которая будет выполняться последовательно.