Reputation: 319
I am using NodeJS (ExpressJS) server, have the following code:
let caseScore = 0
await questions.map(async q => {
caseScore += await q.grade() // Queries database and gets questions, finds score
}))
console.log(caseScore)
>> Output: 0
However, it appears that q.grade() finishes executing after the request is finished. I put a console.log() within q.grade, and it shows up after response is sent. Clearly this is executed asynchronously. So later I did this:
let caseScore = 0
await Promise.all(questions.map(async q => {
caseScore += await q.grade() // Queries database and gets questions, finds score
})))
console.log(caseScore)
>> Output: 2
It works perfectly. Can someone explain to me why Promise.all is needed? Also, if I switch .map for .forEach, Promise.all errors, what is the correct way to do this for .forEach?
Upvotes: 2
Views: 938
Reputation: 780
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();
// STUB
function getSumFromServer() {
return new Promise((resolve) => {
setTimeout(() => {
resolve(Math.random() * 100)
}, 1000)
});
}
Upvotes: 3
Reputation: 4279
Array.prototype.map works like that.
Array.prototype.map = function(callback) {
var results = [];
for (var i = 0; i < this.length; i++) {
results.push(callback(this[i], i));
}
return result;
}
In your code your callback returns Promise
and Array.map returns array of Promise
. Because it doesn't awaits for callback to be completed. Array.forEach also will not work for you because it doesn't awaits callback too.
The best way to solve your problem is using Promise.all
to convert array of Promise
into a single Promise
and await it.
await Promise.all(questions.map(async q => {
caseScore += await q.grade();
}));
Or using simple for
loop like that.
for (var q of questions) {
caseScore += await q.grade();
}
Upvotes: 2
Reputation:
You should have to wait for all async calls to be completed. That is the reason why promise.all needed.
It converts the set of promises to a single promise.
Upvotes: 4