When the interpreter comes across the await
, it will pause the function until the resolution of the Promise. Even if the Promise resolves immediately, the function will only resume during the next microtask. In contrast, the array is iterated through immediately, synchronously. When you do
const promises = arr.map(async () => {
count += await somePromise();
})
After the array is iterated through, but before the await
s have resolved, the "current" value of count
which is taken for use by the +=
is retrieved before the await
resolves - and the value of count
before then is 0. So, it looks to the interpreter as if there are 5 separate statements:
count += await somePromise();
count += await somePromise();
count += await somePromise();
count += await somePromise();
count += await somePromise();
which resolve to something like
const currentValueOfCount = count;
count = currentValueOfCount + await somePromise();
count = currentValueOfCount + await somePromise();
count = currentValueOfCount + await somePromise();
count = currentValueOfCount + await somePromise();
count = currentValueOfCount + await somePromise();
So, each time, the right-hand side of the =
resolves to 0 + 1
, so at the end of the loop, count
is only 1.
If you're interested where this is described in the specification, look at the semantics for assignment operators. Where +=
is one of the AssignmentOperator
s, the following syntax:
LeftHandSideExpression AssignmentOperator AssignmentExpression
does:
- Let lref be the result of evaluating LeftHandSideExpression.
- Let lval be ? GetValue(lref).
- Let rref be the result of evaluating AssignmentExpression.
- Let rval be ? GetValue(rref).
- Let op be the @ where AssignmentOperator is @=.
- Let r be the result of applying op to lval and rval as if evaluating the expression lval op rval.
See how lval
is retrieved immediately, before the right-hand side of the operator is evaluated. (If lval
had been retrieved after the right-hand side, the AssignmentExpression
, is evaluated, the results would have been 5, as you're expecting)
Here's an example of this behavior without asynchronous operations:
let num = 5;
const fn = () => {
num += 3;
return 0;
}
num += 2 + fn();
console.log(num);
Above, the num += 2 + fn();
retrieves num
as 5
immediately for use in +=
, then calls fn()
. Although num
is reassigned inside fn
, it doesn't have any effect, because the value of num
has already been retrieved by the outer +=
.
With your working code, when you do
const nb = await somePromise();
count += nb;
This will put the resolve value of somePromise
into the nb
variable, and then count += nb;
will run. This behaves as expected because the "current" value of count
used for +=
is retrieved after the Promise resolves, so if a prior iteration reassigned count
, it'll be successfully taken into account by the next iteration.