JavaScript Promise:按顺序执行Promise

3

为了更清楚地理解Promise,我阅读了一些相关的文章。我发现以下代码可以完美地按顺序执行Promise,但我不太明白它是如何工作的。

 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 result;
 }

 executeSequentially(promiseFactories)

我理解 Promise 会在创建时立即执行。但是我不太理解执行的流程,特别是下面这行代码:

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

请问是否有人能够帮助我理解为什么在空承诺的“then”方法中调用promiseFactory方法可以使其按顺序执行,就像这样。还是因为forEach循环的缘故?
result = result.then(promiseFactory);

我尝试使用'map'函数替换'forEach',但仍然得到了相同的结果。即方法按顺序执行。 另外,如何将值从一个链接的函数传递给另一个函数?
非常感谢您提供任何帮助或文章/博客。
4个回答

3

你可以把Promise想象成一个内部有执行内容的盒子。一旦Promise被创建,就会开始执行。要获取结果值,您必须打开盒子。您可以使用then实现:

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

如果想要链式地使用 promise,你可以逐个打开盒子:
Promise.resolve(5)
  .then(result => Promise.resolve(result + 1))
  .then(result => Promise.resolve(result * 2))
  .then(result => console.log(result));  // prints 12

这种链式调用使执行同步进行(一个接一个地执行)。

如果你想要同时异步执行多个Promise(不需要链接结果),可以使用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));

另一个处理Promise对象的方法是使用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 可以直接将值打包为 Promise:

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

谢谢回复。那么可以安全地理解,在使用await在循环中执行promises可以确保它们按顺序执行吗? - Nidhin Raj
是的,绝对没错。await 同步解析承诺。 - ttulka
另外,@ttulka,你是否同意promise.all()总是并行执行?顺便说一下,感谢你提供的简洁明了的解释 :-) - Nidhin Raj
是的,Promise.all 异步等待所有 Promise 完成,然后按照参数给定的顺序将解决结果作为数组返回。 - ttulka

2

如果您想要这样的行为,建议始终使用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);


6
Promise.all() 方法会并行执行方法,而非按顺序执行。 - Nidhin Raj
@NidhinRaj添加了那段代码。请检查更新后的答案。 - Ankit Agarwal
谢谢。我正在寻找一个关于如何按顺序运行的解释。为什么var result = Promise.resolve();在循环外面?为什么循环内的Promise.resolve().then(promiseFactory)不起作用?另外,尝试将所有工厂的超时值设置为1000。你会发现所有的promise都是并行运行的。 - Nidhin Raj
@NidhinRaj 我们不需要 var result = Promise.resolve(); - Ankit Agarwal
@NidhinRaj 如果超时时间为1秒,则会将这些超时时间初始化为1秒,并且所有的Promise都在1秒内同时解决。因此,为了使它们看起来是顺序执行的,我已经更改了超时值。 - Ankit Agarwal
@AnkitAgarwal executeSequentially 仍然会并行执行,因为在执行下一个 promiseFactory 时,它没有等待前一个 promiseFactory 的承诺。它的行为与并行代码完全相同。 - Matthias

2

executeSequentially方法会按照顺序返回所有的Promise。它会遍历promiseFactory,但也可以这样写:

最初的回答:

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

这其实是一样的,我们基本上是在返回一个Promise。
现在,我们想要遍历一组Promises。
在迭代时,我们需要使用then将当前Promise附加到前一个Promise上。但是forEach在每次迭代中都不会公开下一个Promise或上一个Promise。然而,我们仍然需要它来保持一个接一个地链接Promises。因此,出现了result“hack”:
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 result;
}

现在,还有一个语法问题可能会让你感到困惑: "Original Answer" 的翻译是“最初的回答”。
result = result.then(promiseFactory);

这行代码与下面的代码几乎相同:

Original Answer

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

请问有人能帮助我理解为什么在空Promise的'then'方法中调用promiseFactory方法会使它按顺序执行,就像这样。还是因为forEach循环的缘故?

首先,promiseFactory是一个不太好的名称。该方法应该更好地编写如下:


"最初的回答":首先,`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 result;
}

所以:
在空 Promise 的 'then' 方法中调用 currentPromise 方法是如何使其按顺序执行的?
当您通过 then 将 Promise 附加到另一个 Promise 时,它会按顺序执行。 这是一个 then 的特性,与我们迭代 Promise 无关。 对于普通的 Promise,它的工作方式基本相同:
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

谢谢你的回答。现在我清楚地理解了这段代码片段。 - Nidhin Raj

1
如果我们展开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()// 创建一个空的 promise。 这只是为了获取 promise 链的起始点。在这里,它是一个已经解决的 promise。为了更好地理解,您可以使用其中一个 promise 来获取 promise 链,如下所示。
let promiseFactories= [doSecondThing, doThirdThing];

let result = doFirstThing();

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

这也可以工作。

谢谢你的回答。这正是我想要理解的关键点。关于你的代码,如果我们将所有超时设置为1000,似乎所有的Promise都会同时运行。只有当它们在循环中时,它们才能按顺序运行。我的理解正确吗? - Nidhin Raj
正确,因为循环创建了一个动态的 Promise 链,将按顺序运行。 - Senal

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接