97

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.

Henke
  • 1,466
  • 2
  • 9
  • 22
Ganbin
  • 1,523
  • 1
  • 10
  • 19
  • Are you in control of the asynchronous calls? Do they already return promises, or can you make them return promises? – T.J. Crowder Jul 15 '15 at 09:42
  • 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? – Sosdoc Jul 15 '15 at 09:43
  • 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. – Ganbin Jul 15 '15 at 09:44
  • 1
    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`. – T.J. Crowder Jul 15 '15 at 10:10

5 Answers5

190

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]
T.J. Crowder
  • 879,024
  • 165
  • 1,615
  • 1,639
  • Ok thanks I try this now and I come with feedback in few minutes. – Ganbin Jul 15 '15 at 10:00
  • 12
    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. – Ganbin Jul 15 '15 at 10:22
  • 1
    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)` – OK sure Jan 12 '18 at 12:15
  • @TJ now how i can render the result data to the view and there i can do the loop to show the data – Ajit Singh Sep 07 '18 at 10:11
  • if `doSomethingAsync(i)` returns a promise how to wait for that to finish along with others in the loop? – noobCoder Mar 06 '19 at 20:44
  • @noobCoder - [All you could possibly want to know about combining and iterating promises in series or parallel](https://stackoverflow.com/questions/14220321/how-do-i-return-the-response-from-an-asynchronous-call/43766002#43766002). :-) – T.J. Crowder Mar 06 '19 at 21:12
  • 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? – user1063287 Jul 24 '19 at 09:24
  • 1
    @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.) – T.J. Crowder Jul 24 '19 at 09:37
  • 1
    Thanks, worked for me! – Abbas.moosavi Feb 11 '21 at 10:21
  • 1
    Thanks @Henke! I've fixed the link. – T.J. Crowder May 21 '21 at 11:12
8

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));
2Toad
  • 13,711
  • 7
  • 38
  • 38
  • 1
    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! – Shaun Vermaak Jun 08 '20 at 01:55
2
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)),
]);
JoeTidee
  • 17,573
  • 19
  • 82
  • 120
2

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!");
    }
)
Mina Michael
  • 1,599
  • 1
  • 18
  • 32
1

/*** 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
Sourav Purkait
  • 146
  • 1
  • 5