如何返回多个Promise并在执行其他操作之前等待它们全部完成

143

我有一个循环,它调用一个异步执行的方法。这个循环可能会多次调用这个方法。在这个循环之后,我有另一个循环,只有当所有异步操作都完成时才需要执行。

所以这里是我的需求:

for (i = 0; i < 5; i++) {
    doSomeAsyncStuff();    
}

for (i = 0; i < 5; i++) {
    doSomeStuffOnlyWhenTheAsyncStuffIsFinish();    
}

我对Promise不是非常熟悉,所以有没有人可以帮助我实现这个?

这是我的doSomeAsyncStuff()的行为:

function doSomeAsyncStuff() {
    var editor = generateCKEditor();
    editor.on('instanceReady', function(evt) {
        doSomeStuff();
        // There should be the resolve() of the promises I think.
    })
}

也许我需要做这样的事情:

function doSomeAsyncStuff() {
    var editor = generateCKEditor();
    return new Promise(function(resolve,refuse) {
        editor.on('instanceReady', function(evt) {
            doSomeStuff();
            resolve(true);
        });
    });
}

但我不确定语法是否正确。


你是否掌控异步调用?它们是否已经返回了 Promises,或者你能否让它们返回 Promises? - T.J. Crowder
什么是序列?在所有先前的异步函数完成后,您需要调用其他函数吗?还是只需要在每个异步函数完成后调用一个函数? - Vale
目前第一个函数没有返回承诺。我必须实现它。我想编辑我的消息,以添加一些有关我的函数工作流程的详细信息。是的,我需要在开始执行第二个循环中的内容之前完成第一个循环中所有的内容。 - Ganbin
1
关于您的编辑:“也许我必须做类似的事情”是的,非常像那样,只是Promise末尾没有s - T.J. Crowder
6个回答

260
你可以使用Promise.all规范MDN)来实现这个功能:它接受一系列的不同promise,并返回一个单一的promise,在你给定的所有promise都得到解决时,它被解决;当其中任何一个promise被拒绝时,它被拒绝。

所以,如果你让doSomeAsyncStuff返回一个promise,那么:

    const promises = [];
//  ^^^^^−−−−−−−−−−−−−−−−−−−−−−−−−−− use `const` or `let`, not `var`
    
    for (let i = 0; i < 5; i++) {
//       ^^^−−−−−−−−−−−−−−−−−−−−−−−− added missing declaration
        promises.push(doSomeAsyncStuff());
    }
    
    Promise.all(promises)
        .then(() => {
            for (let i = 0; i < 5; i++) {
//               ^^^−−−−−−−−−−−−−−−− added missing declaration
                doSomeStuffOnlyWhenTheAsyncStuffIsFinish();    
            }
        })
        .catch((e) => {
            // handle errors here
        });

MDN有一篇关于 Promise 的文章,在这里。另外,如果你感兴趣的话,我在我的书 JavaScript: The New Toys 的第8章中也详细介绍了 Promise,可以在我的个人资料中找到链接。

 function doSomethingAsync(value) {
     return new Promise((resolve) => {
         setTimeout(() => {
             console.log("Resolving " + value);
             resolve(value);
         }, Math.floor(Math.random() * 1000));
     });
   }
   
   function test() {
       const promises = [];
       
       for (let i = 0; i < 5; ++i) {
           promises.push(doSomethingAsync(i));
       }
       
       Promise.all(promises)
           .then((results) => {
               console.log("All done", results);
           })
           .catch((e) => {
               // Handle errors here
           });
   }
   
   test();

以下是示例输出结果(由于 Math.random 的作用,各个部分完成的先后顺序可能会有所不同):

正在解决 3
正在解决 2
正在解决 1
正在解决 4
正在解决 0
全部完成 [0,1,2,3,4]

14
哇,非常感谢,现在我更理解 Promise 了。我读了很多关于 Promise 的资料,但直到我们在真正的代码中使用它们时,我们才真正理解所有机制。现在我更好地理解了,可以开始写酷炫的东西了,感谢你。 - Ganbin
1
另外,如果您想以任何原因(例如模拟进度)按顺序完成这些任务,可以将 Math.floor(Math.random() * 1000) 更改为 (i * 1000) - OK sure
如果doSomethingAsync(i)返回一个Promise,如何等待它和循环中的其他Promise完成? - noobCoder
1
@user1063287 - 如果代码所处的上下文允许使用await,那么您可以这样做。目前,您只能在async函数内部使用await。(在某个时候,您还可以在模块的顶层使用它。) - T.J. Crowder
显示剩余2条评论

14

一个可重用的函数可以很好地解决这个模式:

function awaitAll(count, asyncFn) {
  const promises = [];

  for (i = 0; i < count; ++i) {
    promises.push(asyncFn());
  }

  return Promise.all(promises);
}

OP的例子:

awaitAll(5, doSomeAsyncStuff)
  .then(results => console.log('doSomeStuffOnlyWhenTheAsyncStuffIsFinished', results))
  .catch(e => console.error(e));
一个相关的模式是对数组进行迭代,并在每个项目上执行异步操作:
function awaitAll(list, asyncFn) {
  const promises = [];

  list.forEach(x => {
    promises.push(asyncFn(x));
  });

  return Promise.all(promises);
}

示例:

const books = [{ id: 1, name: 'foo' }, { id: 2, name: 'bar' }];

function doSomeAsyncStuffWith(book) {
  return Promise.resolve(book.name);
}

awaitAll(books, doSomeAsyncStuffWith)
  .then(results => console.log('doSomeStuffOnlyWhenTheAsyncStuffIsFinished', results))
  .catch(e => console.error(e));

2
这确实使代码更易于理解和更清晰。我认为当前的示例(显然是根据OP的代码进行调整的)并没有充分展示这一点。这是一个很棒的技巧,谢谢! - Shaun Vermaak

4

/*** Worst way ***/
for(i=0;i<10000;i++){
  let data = await axios.get(
    "https://yourwebsite.com/get_my_data/"
  )
  //do the statements and operations
  //that are dependant on data
}

//Your final statements and operations
//That will be performed when the loop ends

//=> this approach will perform very slow as all the api call
// will happen in series


/*** One of the Best way ***/

const yourAsyncFunction = async (anyParams) => {
  let data = await axios.get(
    "https://yourwebsite.com/get_my_data/"
  )
  //all you statements and operations here
  //that are dependant on data
}
var promises = []
for(i=0;i<10000;i++){
  promises.push(yourAsyncFunction(i))
}
await Promise.all(promises)
//Your final statement / operations
//that will run once the loop ends

//=> this approach will perform very fast as all the api call
// will happen in parallal


3
const doSomeAsyncStuff = async (funcs) => {
  const allPromises = funcs.map(func => func());
  return await Promise.all(allPromises);
}

doSomeAsyncStuff([
  () => new Promise(resolve => setTimeout(() => resolve(), 100)),
  () => new Promise(resolve => setTimeout(() => resolve(), 100)),
  () => new Promise(resolve => setTimeout(() => resolve(), 100)),
  () => new Promise(resolve => setTimeout(() => resolve(), 100)),
  () => new Promise(resolve => setTimeout(() => resolve(), 100)),
]);

3

这是我为了理解这里所述答案而编写的代码。我在一个for循环中使用mongoose查询,因此我在这里放置了asyncFunction来代替它。希望它能帮助任何人。您可以在node或任何许多Javascript运行时中运行此脚本。

let asyncFunction = function(value, callback)
{
        setTimeout(function(){console.log(value); callback();}, 1000);
}



// a sample function run without promises

asyncFunction(10,
    function()
    {
        console.log("I'm back 10");
    }
);


//here we use promises

let promisesArray = [];

let p = new Promise(function(resolve)
{
    asyncFunction(20,
        function()
        {
            console.log("I'm back 20");
            resolve(20);
        }
    );
});

promisesArray.push(p);


for(let i = 30; i < 80; i += 10)
{
    let p = new Promise(function(resolve)
    {
        asyncFunction(i,
            function()
            {
                console.log("I'm back " + i);
                resolve(i);
            }
        );
    });
    promisesArray.push(p);
}


// We use Promise.all to execute code after all promises are done.

Promise.all(promisesArray).then(
    function()
    {
        console.log("all promises resolved!");
    }
)

0

如果您想要多次执行相同的操作,这里有一个优雅的解决方案:

await Promise.all(new Array(10).fill(0).map(() => asyncFn()));

这将创建一个包含10个元素的数组,用零填充它,然后将其映射到一个Promise数组。


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