Prom.all inside the forEach loop - everything works right away

In a Node application, I need to iterate over some elements synchronously, but some of the operations inside the loop are asynchronous. My code now looks like this:

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

This works when items is an array of 1. But, if there is more than one element, promise.all() will immediately be run immediately for each element in the array, without waiting for the operation in the loop to end.

All that to say ... how can I guarantee that the entire operation for each element of the array is synchronous (even if some operations are asynchronous and return a promise)?

Thank you very much!

N

+5
source share
5 answers

Everything is correct ... the way we were able to make it work: array.reduce () using Promises, Final result:

 myAsyncAPIcall.then(items => { items.reduce((current, nextItem) => { return current.then(() => { return new Promise(res => { Promise.all([myPromiseA(nextItem), myPromiseB(nextItem]).then(() => { someSynchronousCallThatTakesAWhile(nextItem); res(); }).catch(err => { console.log(err); }); }); }); }, Promise.resolve()) }) 

The way it works is that by wrapping each element of the array in its Promise (allow, reject) , we can guarantee that each iteration is performed synchronously, since the completion of one iteration will necessitate the resolution of the next promise, etc. etc. In each resolution of promises, calls can be launched asynchronously for as long as you want, knowing that they will only be tied to the parent promise until it ends.

Hope this helps people!

+1
source

You create several promises, but they are all asynchronous. You build Promise1, Promise2, Promise3, ... but as soon as they are in the wild, they all shoot at the same time. If you want synchronous behavior, you need to bind them together, so that Promise1.then () does Promise2 and so on. I used to use 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 want, which can make everything clearer.

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

This function is performed as

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

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

The auxiliary function approach is also less invasive of change. The helper applies to your source code:

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

You can remove .forEach() ; use Array.prototype.reduce() to return an array of Promise values ​​in Promise.all() . If the item with items is a function, call the function, else wrap in Promise.resolve() , which should return the results in the same order as in the items array

See Promise.all()

Promise.all passes an array of values ​​from all promises to the iterative object that was passed. An array of values ​​supports the order of the original iterable, rather than the order in which promises were allowed. If something passing in the iterable array is not promised, it is converted to a single Promise.resolve .

 var arr = [1, // not asynchronous function j() { return new Promise(function(resolve) { setTimeout(function() { resolve(2) }, Math.floor(Math.random() * 10000)) }) }, // asynchronous 3, // not asynchronous function j() { return new Promise(function(resolve) { setTimeout(function() { resolve(4) }, Math.floor(Math.random() * 3500)) }) }, // asynchronous 5, // not asynchronous Promise.resolve(6), // asynchronous 7 ]; Promise.all(arr.reduce(function(p, next) { var curr = Promise.resolve(typeof next === "function" ? next() : next); return p.concat.apply(p, [curr.then(function(data) { console.log(data); return data })]); }, [])) .then(function(data) { console.log("complete", data) }) 

An alternative approach would be to use Array.prototype.shift() , Promise.resolve() , .then() , recursion

 function re(items, res) { if (items.length) { var curr = items.shift(); return Promise.resolve( typeof curr === "function" ? curr() : curr ).then(function(data) { // values from `arr` elements should be logged in sequential order console.log(data); res.push(data) }).then(re.bind(null, items, res)) } else { return ["complete", res] } } var _items = arr.slice(0); re(_items, []) .then(function(complete) { console.log(complete) }) 

 var arr = [1, // not asynchronous function j() { return new Promise(function(resolve) { setTimeout(function() { resolve(2) }, Math.floor(Math.random() * 10000)) }) }, // asynchronous 3, // not asynchronous function j() { return new Promise(function(resolve) { setTimeout(function() { resolve(4) }, Math.floor(Math.random() * 3500)) }) }, // asynchronous 5, // not asynchronous Promise.resolve(6), // asynchronous 7 ]; function re(items, res) { if (items.length) { var curr = items.shift(); return Promise.resolve( typeof curr === "function" ? curr() : curr ).then(function(data) { // values from `arr` elements should be logged in sequential order console.log(data); res.push(data) }).then(re.bind(null, items, res)) } else { return ["complete", res] } } var _items = arr.slice(0); re(_items, []) .then(function(complete) { console.log(complete) }) 
+1
source

How about saving forEach ...

 var stopAllProcessingOnServerLowValue= false; function someAPIpromise(){ var arr = [ {id:123, urlVal:null}, {id:456, urlVal:null}, {id:789, urlVal:null}, {id:101112, urlVal:null} ]; return new Promise(function(resolve){ setTimeout(function(){ resolve(arr) }, 3000); }) } function extractSomeValueRemotely(url){ return new Promise(function(resolve, reject){ console.log("simulate an async connection @ %s to request a value", url); setTimeout(function(){ var someRandom = Math.round(Math.random()*7) + 1; console.log("%s responded with %s", url, someRandom); if(someRandom > 4){ resolve(someRandom); } else{ var issue = "Urls result is too low ("+someRandom+" <= 4)."; console.warn(issue+".It will be set to -1"); if(stopAllProcessingOnServerLowValue){ reject(issue+".Operation rejected because one or mole server results are too low ["+someRandom+"]."); } else{ resolve(-1); } } }, 1500*Math.round(Math.random()*7) + 1); }); } function addAnotherExtraParamToItem(_item){ return new Promise(function(resolve, reject){ setTimeout(function(){ console.log("setting extra2 on %s", _item.id); _item['extra'] = "additional_processing_"+_item.id; resolve(_item); }, 1500*Math.round(Math.random()*5) + 1); }); } function addOrderIndexToItem(_item, _order){ return new Promise(function(resolve, reject){ setTimeout(function(){ console.log(">> setting order %s on %s",_order, _item.id); _item['order'] = _order; resolve(_item); }, 1500*Math.round(Math.random()*3) + 1); }); } someAPIpromise().then(function(items){ var perItemPromises = []; items.forEach(function(item, idx){ perItemPromises.push( new Promise(function(pulseItemResolve, pulseItemReject){ var itemStepsPromises = []; itemStepsPromises.push(addAnotherExtraParamToItem(item)); itemStepsPromises.push(extractSomeValueRemotely("http://someservice:777/serve-me") .catch( function(reason){ //the entire item will be rejected id pulseItemReject(reason); }) ); itemStepsPromises.push(addOrderIndexToItem(item, idx)); //promise that ensure order of execution on all previous async methods Promise.all(itemStepsPromises).then(function(values){ //0 - first is result from addAnotherExtraParamToItem var theItem = values[0]; //it returns the item itself //urlVal has not been set yet // 1 - second promise return the url result var serverResult = values[1]; //2 - third promise add the order index but we do not care to inspect it because theItem reference in value[0] has been already updated. // console.info(values[2]); //sets the url result in the item theItem.urlVal = serverResult; console.log("urlVal set to:", theItem.urlVal); //resolve the prepared item pulseItemResolve(theItem); }); }) .catch(function(reason){ //escalate error throw new Error(reason); }) ) }); Promise.all(perItemPromises).then(function(resultsInAllItems){ console.info("Final results:"); console.info(resultsInAllItems); }).catch(function(finalReject){ console.error("Critical error:",finalReject); }) }); 
0
source

After much research, the final answer for me was here ...

I read bundles of solutions for using useful pure JavaScript (without add-ons) -Promise Iterator, which could be easily used (one line) for all my projects, and finally I found this solution by Salketer:

 function one_by_one(objects_array, iterator, callback) { var start_promise = objects_array.reduce(function (prom, object) { return prom.then(function () { return iterator(object); }); }, Promise.resolve()); // initial if(callback){ start_promise.then(callback); }else{ return start_promise; } } 

For more details and an example of use, visit the link .

It also allows you to handle the callback directly.

It was just the most logical and reusable method that I found after many days of struggling with Promise iterations and testing MULTIPLE solutions from many questions, blogs and official sites.

If you are also struggling for a definitive answer, try it.

0
source

All Articles