赋值运算符、map 和 promises。那段代码有什么问题?JavaScript
我在做一些事情时遇到了一个我无法理解的问题。我简化了代码,得到如下结果:
function somePromise() {
return new Promise((resolve, reject) => {
resolve(1);
});
}
async function main() {
let count = 0;
const arr = [1, 2, 3, 4, 5];
const promises = arr.map(async () => {
count += await somePromise();
})
await Promise.all(promises);
console.log(count);
}
main().then(() => console.log('Done'));你期望什么结果?
1
完成
已记录。
当我改变时
count += await somePromise();
到
const nb = await somePromise();
count += nb;
我明白
5
完成
我第一次所期望的。
你能帮我找出哪里有问题吗?我不明白。
答案#1
当解释器遇到 await 时,它会暂停函数,直到 Promise 被解决。即使 Promise 立即解决,函数也只会在下一个微任务中恢复。相比之下,数组是立即同步迭代的。当你做
const promises = arr.map(async () => {
count += await somePromise();
})
在数组被迭代完之后,但在 await 解决之前,用于 += 的 count 的“当前”值是在 await 解决之前获取的——而此时 count 的值是 0。因此,对于解释器来说,看起来像是有 5 条单独的语句:
count += await somePromise();
count += await somePromise();
count += await somePromise();
count += await somePromise();
count += await somePromise();
解析为类似于
const currentValueOfCount = count;
count = currentValueOfCount + await somePromise();
count = currentValueOfCount + await somePromise();
count = currentValueOfCount + await somePromise();
count = currentValueOfCount + await somePromise();
count = currentValueOfCount + await somePromise();
所以,每次,= 右侧都会解析为 0 + 1,所以在循环结束时,count 只有 1。
如果你有兴趣了解在规范中是如何描述的,请查看赋值运算符的语义。其中 += 是 AssignmentOperator 之一,以下语法:
左侧表达式 赋值运算符 赋值表达式
做:
- 令 lref 为评估 LeftHandSideExpression 的结果。
- 设 lval=? GetValue(lref)。
- 让 rref 成为评估 AssignmentExpression 的结果。
- 让 rval=? GetValue(rref)。
- 注意 @ 其中赋值运算符是 @=。
- 设 r 为将 op 应用于 lval 和 rval 的结果,就像求值表达式 lval op rval 一样。
看看 lval 是如何在运算符的右侧被计算之前 立即 被获取的。(如果 lval 是在右侧 AssignmentExpression 被计算 之后 才被获取,结果将会是你所期望的 5)
这是这种行为的一个例子,没有异步操作:
let num = 5;
const fn = () => {
num += 3;
return 0;
}
num += 2 + fn();
console.log(num);上面,num += 2 + fn(); 会立即将 num 取为 5 用于 +=,然后调用 fn()。虽然 num 在 fn 内被重新赋值,但没有任何影响,因为 num 的值已经被外部的 += 取用了。
使用你的工作代码时,当你执行
const nb = await somePromise();
count += nb;
这将把 somePromise 的解析值放入 nb 变量,然后 再 执行 count += nb;。这是预期的行为,因为用于 += 的 count 的“当前”值是在 Promise 解析 之后 获取的,因此如果前一次迭代重新分配了 count,下次迭代也会成功考虑到这个值。
