Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

promise.all inside a forEach loop — everything firing at once

In a Node app, I need to iterate through some items in a synchronous fashion, but some of the operations inside the loop are asynchronous. My code right now looks like so:

someAPIpromise().then((items) => {
   items.forEach((item) => {
      Promise.all[myPromiseA(item), myPromiseB(item)]).then(() => {
         doSomethingSynchronouslyThatTakesAWhile();
      });
    }
}

This works wonders when the items is an array of 1. But, once there's more than one item, promise.all() will just fire off instantly for every item in the array, without waiting for the operation in the loop to end.

All that to say... how can I ensure that the entire operation for each item in the array is run synchronously (even if some operations are async and return a promise)?

Thanks so much!

N

like image 208
napo Avatar asked May 07 '16 14:05

napo


People also ask

Does forEach return Promise?

JavaScript Promises forEach with promises It is possible to effectively apply a function ( cb ) which returns a promise to each element of an array, with each element waiting to be processed until the previous element is processed.

What does Promise all () do?

all() The Promise. all() method takes an iterable of promises as an input, and returns a single Promise that resolves to an array of the results of the input promises. This returned promise will fulfill when all of the input's promises have fulfilled, or if the input iterable contains no promises.

How do you handle promises inside a loop?

To use Javascript promises in a for loop, use async / await . This waits for each promiseAction to complete before continuing to the next iteration in the loop. In this guide, you learn how async/await works and how it solves the problem of using promises in for loops.


1 Answers

You're constructing several promises, but they are all asynchronous. You construct Promise1, Promise2, Promise3, ... but once they're in the wild they are all firing simultaneously. If you want synchronous behavior you've got to chain them together so Promise1's .then() executes Promise2 and so on. In the past I've used Array.reduce for this.

someAPIpromise().then((items) => {
    items.reduce((accumulator, current) =>
        accumulator.then(() =>
             Promise.all[myPromiseA(item), myPromiseB(item)]).then(() => 
                 doSomethingSynchronouslyThatTakesAWhile();
             )
        )
    , Promise.resolve());

You can write this as a helper function if you like, which may make things clearer.

function execSequentially (arr, func) {
    return arr.reduce(
        (accumulator, current) => accumulator.then(() => func(current)), 
        Promise.resolve());
}

That function is executed as

execSequentially(items, item => console.log(item));

of course replacing console.log with what you want to do.

The helper function approach is also less invasive of a change. The helper applied to your original code:

someAPIpromise().then((items) => {
   execSequentially(items, (item) =>
      Promise.all[myPromiseA(item), myPromiseB(item)]).then(() => {
         doSomethingSynchronouslyThatTakesAWhile();
      });
   );
});
like image 128
Paarth Avatar answered Nov 14 '22 01:11

Paarth