TL;DR: 因为+=
在其第二操作数(右侧)中有await
关键字,导致它在更改x
之前读取了它,但在更改之后才写入。
async
函数在调用时同步运行,直到第一个await
语句。
因此,如果删除await
,它的行为就像普通函数一样(唯一的例外是它仍然返回Promise)。
在这种情况下,您将在控制台中获得5
(来自函数)和6
(来自主脚本):
let x = 0;
async function test() {
x += 5;
console.log('x :', x);
}
test();
x += 1;
console.log('x :', x);
await
关键字会使同步代码挂起,即使它的参数已经是一个已解决的 promise(或者像此处一样,根本不是一个 promise - 这些将被 await
转换为已解决的 promise)。因此,以下代码会如你所预期的返回 1
(来自主脚本)和 6
(来自函数):
let x = 0;
async function test() {
await 0;
x += 5;
console.log('x :', x);
}
test();
x += 1;
console.log('x :', x);
然而,你的情况稍微有些复杂。
你在一个使用 +=
的表达式中放置了 await
。
你可能知道,在JS中,x += y
等同于 x = (x + y)
(除非 x
是一个具有副作用的表达式,但这里不是这种情况)。我将使用后者形式以使其更易于理解:
let x = 0;
async function test() {
x = (x + await 5);
console.log('x :', x);
}
test();
x += 1;
console.log('x :', x);
当解释器执行到这行代码时...
x = (x + await 5);
...它开始评估它,替换x
,因此它变成了...
x = (0 + await 5);
接着,它会评估await
内部的表达式(5
),将其转换为已解决的 Promise,并开始等待。
函数调用后的代码开始运行,并修改了x
的值(从0
变为1
),然后将其记录下来。
x
现在是1
。
然后,在主脚本完成之后,解释器返回到暂停的test
函数,并继续评估该行,这样看起来:
x = (0 + 5)
而且,由于x
的值已经被替换,它仍然是0
。
最后,解释器进行加法运算,将5
存储到x
并记录它。
您可以通过在对象属性getter/setter内部(在本例中为y.z
,它反映了x
的值)进行记录来检查此行为:
let x = 0;
const y = {
get z() {
console.log('get x :', x);
console.log(new Error().stack.replace('Error', 'Stacktrace'));
return x;
},
set z(value) {
console.log('set x =', value);
console.log(new Error().stack.replace('Error', 'Stacktrace'));
x = value;
}
};
async function test() {
console.log('inside async function');
y.z += await 5;
console.log('x :', x);
}
test();
console.log('main script');
y.z += 1;
console.log('x :', x);
console.log('end of main script')
.as-console-wrapper {
max-height: 100% !important;
}
await (x += 5)
和x += await 5
的区别。 - Singhi John