网站搜索

赋值运算符、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 之一,以下语法:

左侧表达式 赋值运算符 赋值表达式

做:

  1. 令 lref 为评估 LeftHandSideExpression 的结果。
  2. 设 lval=? GetValue(lref)。
  3. 让 rref 成为评估 AssignmentExpression 的结果。
  4. 让 rval=? GetValue(rref)。
  5. 注意 @ 其中赋值运算符是 @=。
  6. 设 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,下次迭代也会成功考虑到这个值。