如何在做其他事情之前返回许多 Promise 并等待它们

Posted

技术标签:

【中文标题】如何在做其他事情之前返回许多 Promise 并等待它们【英文标题】:How to return many Promises and wait for them all before doing other stuff 【发布时间】:2021-08-11 01:55:31 【问题描述】:

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

所以这说明了我想要什么:

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);
        );
    );

但我不确定语法。

【问题讨论】:

您能控制异步调用吗?他们是否已经返回承诺,或者你可以让他们返回承诺? 顺序到底是什么? all 之前的异步函数完成后是否需要调用其他函数?还是只需要在每个异步完成后调用一个函数? 目前第一个函数不返回承诺。我必须执行。我想编辑我的消息以添加我的功能工作流程的一些细节。是的,我需要在开始执行第二个循环中的内容之前完成第一个循环的所有内容。 重新编辑:“也许我必须做类似的事情” 是的,非常类似,只是 Promise 的末尾没有 s 【参考方案1】:

您可以使用 Promise.all (spec, MDN) 来实现:它接受一堆单独的 Promise,并返回一个在您给它的所有 Promise 都已解决时已解决的 Promise,或者当其中任何一个被拒绝时被拒绝。

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

    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 的文章 here。我还在我的书 javascript: The New Toys 的第 8 章中详细介绍了 Promsies,如果您有兴趣,可以在我的个人资料中链接。

这是一个例子:

 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]

【讨论】:

好的,谢谢,我现在试试这个,几分钟后我会收到反馈。 哇,非常感谢,现在我对 Promise 了解得更多了。我阅读了很多关于 Promise 的内容,但是在我们需要在实际代码中使用它们之前,我们并不真正了解所有的机制。现在我变得更好了,我可以开始写很酷的东西了,谢谢你。 另外,如果您出于任何原因(例如模拟进度)想要按顺序完成这些任务,您可以将Math.floor(Math.random() * 1000) 更改为(i * 1000) @user1063287 - 如果代码在允许await 的上下文中,您可以这样做。目前,您可以使用await 的唯一位置是在async 函数内。 (在某些时候,您还可以在***模块中使用它。) 谢谢@Henke!我已经修复了链接。【参考方案2】:

一个可重用的函数非常适合这种模式:

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));

【讨论】:

这确实使代码更易于理解和清洁。我不认为当前的示例(显然已适应 OP 的代码)做到了这一点。这是一个巧妙的技巧,谢谢!【参考方案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)),
]);

【讨论】:

【参考方案4】:

这是我为自己编写的代码,以便理解此处所述的答案。我在 for 循环中有 mongoose 查询,所以我在这里放了 asyncFunction 来代替它。希望它可以帮助任何人。您可以在节点或许多 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!");
    
)

【讨论】:

【参考方案5】:

/*** 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

【讨论】:

【参考方案6】:

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

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

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

【讨论】:

以上是关于如何在做其他事情之前返回许多 Promise 并等待它们的主要内容,如果未能解决你的问题,请参考以下文章

在做其他事情的同时在 C++ 中播放声音

如何使用其他Promise创建Promise?

Ember.RSVP.all 似乎立即解决

promise 的基本概念 和如何解决js中的异步编程问题 对 promis 的 then all ctch 的分析 和 await async 的理解

Javascript等待Promise在返回响应之前完成for循环

Mongoose:用 promise 编写自己的方法