How to return many Promises and wait for them all before doing other stuff

118,053

Solution 1

You can use Promise.all (spec, MDN) for that: It accepts a bunch of individual promises and gives you back a single promise that is resolved when all of the ones you gave it are resolved, or rejected when any of them is rejected.

So if you make doSomeAsyncStuff return a promise, then:

    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 has an article on promises here. I also cover promsies in detail in Chapter 8 of my book JavaScript: The New Toys, links in my profile if you're interested.

Here's an example:

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

Sample output (because of the Math.random, what finishes first may vary):

Resolving 3
Resolving 2
Resolving 1
Resolving 4
Resolving 0
All done [0,1,2,3,4]

Solution 2

A reusable function works nicely for this pattern:

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

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

  return Promise.all(promises);
}

OP example:

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

A related pattern, is iterating over an array and performing an async operation on each item:

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

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

  return Promise.all(promises);
}

Example:

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

Solution 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)),
]);

Solution 4

Here is code that I wrote for myself in order to understand the answers stated here. I have mongoose queries in a for loop, so I put here the asyncFunction to take its place. Hope it helps anyone. You can run this script in node or any of many Javascript runtimes.

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!");
    }
)

Solution 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

Share:
118,053

Related videos on Youtube

Ganbin
Author by

Ganbin

Javascript Developer http://www.ajar.ch/ http://wakanda.io/ http://wakanda.org/

Updated on July 15, 2021

Comments

  • Ganbin
    Ganbin almost 3 years

    I have a loop which calls a method that does stuff asynchronously. This loop can call the method many times. After this loop, I have another loop that needs to be executed only when all the asynchronous stuff is done.

    So this illustrates what I want:

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

    I'm not very familiar with promises, so could anyone help me to achieve this?

    This is how my doSomeAsyncStuff() behaves:

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

    Maybe I have to do something like this:

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

    But I'm not sure of the syntax.

    • T.J. Crowder
      T.J. Crowder almost 9 years
      Are you in control of the asynchronous calls? Do they already return promises, or can you make them return promises?
    • Vale
      Vale almost 9 years
      What exactly is the sequence? Do you need to call the other functions after all the previous async ones are finished? Or do you just need to call a function after each of the async are finished?
    • Ganbin
      Ganbin almost 9 years
      For now the first function doesn't return promises. That I have to implement. I want to edit my message to add some details of the workflow of my functions. And yes I need that all the stuff of the first loop to be finish before start to execute the stuff in the second loop.
    • T.J. Crowder
      T.J. Crowder almost 9 years
      Re your edit: "Maybe I have to do something like that" Yup, very much like that, except there's no s at the end of Promise.
  • Ganbin
    Ganbin almost 9 years
    Ok thanks I try this now and I come with feedback in few minutes.
  • Ganbin
    Ganbin almost 9 years
    Wow, thanks a lot, now I understand much more the promises.I read a lot about promises, but until we need to use them in real code, we don't really understand all the mechanisms. Now I get it better and I can start to write cool stuff, thanks to you.
  • OK sure
    OK sure over 6 years
    Also, if you want to get these tasks to complete in order for any reason (for example mocking progress), you can change Math.floor(Math.random() * 1000) to (i * 1000)
  • Ajit Singh
    Ajit Singh over 5 years
    @TJ now how i can render the result data to the view and there i can do the loop to show the data
  • noobCoder
    noobCoder about 5 years
    if doSomethingAsync(i) returns a promise how to wait for that to finish along with others in the loop?
  • T.J. Crowder
    T.J. Crowder about 5 years
  • user1063287
    user1063287 almost 5 years
    Instead of Promise.all(promises).then( results) => { } ).catch((e) => { }), is it possible to use try { var results = await Promises.all(promises); // continue with code here } catch (err) { // handle error here }? I am just wondering if both constructs achieve the same thing?
  • T.J. Crowder
    T.J. Crowder almost 5 years
    @user1063287 - You can do that if the code is in a context where await is allowed. At the moment, the only place you can use await is inside an async function. (At some point you'll also be able to use it at the top level of modules.)
  • Shaun Vermaak
    Shaun Vermaak almost 4 years
    This really makes code easier to understand and cleaner. I don't think the current example (which was obviously adapted to OP's code) does this justice. This is a neat trick, thanks!
  • T.J. Crowder
    T.J. Crowder almost 3 years
    Thanks @Henke! I've fixed the link.