Node JS Promise.all and forEach
It's pretty straightforward with some simple rules:
- Whenever you create a promise in a
then
, return it - any promise you don't return will not be waited for outside. - Whenever you create multiple promises,
.all
them - that way it waits for all the promises and no error from any of them are silenced. - Whenever you nest
then
s, you can typically return in the middle -then
chains are usually at most 1 level deep. - Whenever you perform IO, it should be with a promise - either it should be in a promise or it should use a promise to signal its completion.
And some tips:
- Mapping is better done with
.map
than withfor/push
- if you're mapping values with a function,map
lets you concisely express the notion of applying actions one by one and aggregating the results. - Concurrency is better than sequential execution if it's free - it's better to execute things concurrently and wait for them
Promise.all
than to execute things one after the other - each waiting before the next.
Ok, so let's get started:
var items = [1, 2, 3, 4, 5];
var fn = function asyncMultiplyBy2(v){ // sample async action
return new Promise(resolve => setTimeout(() => resolve(v * 2), 100));
};
// map over forEach since it returns
var actions = items.map(fn); // run the function over all items
// we now have a promises array and we want to wait for it
var results = Promise.all(actions); // pass array of promises
results.then(data => // or just .then(console.log)
console.log(data) // [2, 4, 6, 8, 10]
);
// we can nest this of course, as I said, `then` chains:
var res2 = Promise.all([1, 2, 3, 4, 5].map(fn)).then(
data => Promise.all(data.map(fn))
).then(function(data){
// the next `then` is executed after the promise has returned from the previous
// `then` fulfilled, in this case it's an aggregate promise because of
// the `.all`
return Promise.all(data.map(fn));
}).then(function(data){
// just for good measure
return Promise.all(data.map(fn));
});
// now to get the results:
res2.then(function(data){
console.log(data); // [16, 32, 48, 64, 80]
});
How to execute promises simultaneously with forEach?
First, move the code flow that deals with each individual
user
value into its own separateasync
function.- Because of how
async
functions actually work (as eachasync function
marks the boundary of each async state-machine) it isn't really feasible to do concurrent-async work inside afor
statement.
- Because of how
Invoke that function from within the
for
loop.Always prefer
===
over==
.Use TypeScript. Using
async
JavaScript without TypeScript is a world of pain because you need to keep track of which functions returnPromise<T>
vs. those that don't, as well as correctly unwrappingPromise<T>
objects.Always, wherever possible, have your own
try/catch
statement that wraps the entire body of anasync
function, otherwise you'll have to deal with inconsistencies with how different JS engines and JS code handles thrown exceptions and objects.
import usersjson from './jsons/users.json';
async function updateUsers() {
const promises = [];
const users = usersjson;
for( const user of users ) {
const promise = updateUser( user ); // <-- Note this is *NOT* awaited here.
promises.push( promise );
}
await Promise.all( promises ); // <-- This is where we await everything.
}
async function updateUser( user ) {
try {
const data = await getUser( url, user.Id, 'Id' );
if( data.users.length === 0 ) {
let createResult = await newUser( url, user );
console.log( "Created new user %o", createResult );
}
else {
const updateResult = await updateUser( url, user, data.users[0].id );
console.log( "Update user %o: Result: %o", user.Id, updateResult );
}
}
catch( err ) {
console.error( "Failed to update (or create) user with UserId %o: %o", user.userId, err );
}
}
Error using foreach and Promisse.all in node
The problem is that foreach
does not return an array.
I would suggest to use map
instead.
Check the code below:
const users = [1,2,3,4,5];
Promise.all(
users.map(async (element) => {
const userPopulated = {};
userPopulated.hours = Promise.resolve(element);
userPopulated.generalinfo = element;
return element;
})
).then((a) => console.log(a))
Why is Promise.all needed for Array.map Array.forEach asynchronous operation?
When you await
an array of promises, you wait for an array which is not a promise (even though it contain promises inside). await
will immediately resolve values that are not a Promise
. It's like if you did await 'hello'
, that would be resolving instantly.
Promise.all
is a util that exposes a new Promise
that resolves only when all the promises in the array passed as an argument are resolved. Since it's creates it's own promise, you can await
on Promise.all
.
[EDIT]
Careful, do not use await in loops like this
for (var q of questions) {
caseScore += await q.grade();
}
The reason is that it translates to
questions[0].grade().then(score =>
return questions[1].grade().then(score =>
return questions[2].grade().then(score =>
return questions[3].grade().then(score =>
// ...
);
);
);
);
it creates a lots of context in memory and makes everything serial which is not taking advantage of javascript async nature
if you want to have a sum or some values shared between promises you can use a context object
async function grade1() { this.sum += await getSumFromServer(); this.nbOfTasks += 1;}
async function grade2() { this.sum += await getSumFromServer(); this.nbOfTasks += 1;}
async function grade3() { this.sum += await getSumFromServer(); this.nbOfTasks += 1;}
async function main() { const context = { sum: 0, nbOfTasks: 0, } const promisesWithContext = [grade1, grade2, grade3] .map(fn => fn.call(context));
await Promise.all(promisesWithContext); console.log(context);}
main();
// STUBfunction getSumFromServer() { return new Promise((resolve) => { setTimeout(() => { resolve(Math.random() * 100) }, 1000) });}
How to use promise all in lodash forEach and resolve it and do res.send(data)
If you use Object.entries
instead of _.forEach
and .map
to an array of promises, you can use Promise.all
on that:
Promise.all(
Object.entries(req.body).map(value => gridObj.save(value))
).then(/*..*/).catch(/*...*/)
Using Promise.all on my db.collection forEach
async function getUsersGroups(users, req) {
await Promise.all(users.map(user => {
return db.collection("groups")
.find({ "users._id": String(user._id) })
.toArray()
.then(group => {
user.groups = group;
})
}));
return users;
}
Hope this helps
Promises being evaluated before Promise.all in nested forEach, resulting in empty Promise.all
Yeah so the problem I see is that your for each loop is calling asynchronous code and expecting it to execute synchronously.
I'd probably do something like...
var promises = list.map(element => {
return otherModule.getSublist(element).then(sublist => {
// Map all of the sublists into a promise
return Promise.all(sublist.map(subelement => {
return otherOtherModule.promiseResolvingFunction(subelement));
}));
});
});
return Promise.all(promises);
Of course then you'd end up with an array of arrays. If you wanted to keep the result a flat array of sublist items, another option would be to first fetch all of your lists, then fetch all of your sublists from those results...
return Promise.all(list.map( element => otherModule.getSublist(element)))
.then((sublists) => {
let subListPromises = [];
// Loop through each sublist, turn each item in it into a promise
sublists.forEach( sublist => {
sublistPromises = [
...sublistPromises,
sublist.map( subelement => otherOtherModule.promiseResolvingFunction(subelement))
]
})
// Return a promise dependent on *all* of the sublist elements
return Promise.all(sublistPromises)
})
Related Topics
Getting Blob Data from Xhr Request
Method Overloading in JavaScript
Testing Whether a Value Is Odd or Even
Resize a Div on Border Drag and Drop Without Adding Extra Markup
Howto: Div with Onclick Inside Another Div with Onclick JavaScript
Scale Element Proportional to Background Cover with Jquery
JavaScript Code Not Work in Head Tag
Positioning Divs in a Circle Using JavaScript
Bootstrap Modal: Is Not a Function
How to Be Notified Once a Web Font Has Loaded
How to Loop Through All the Elements Returned from Getelementsbytagname
Difference Between Window.Location.Href=Window.Location.Href and Window.Location.Reload()
How to Send Variables from One File to Another in JavaScript
Remove Leading Zeros from a Number in JavaScript
In JavaScript, How to "Clear" the Back (History -1)