Answers:
TL; DR:由于其第二个操作数(右侧)中的关键字,因此在更改之前先+=
读取,x
然后写入await
。
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
停止同步运行,即使其参数可同步使用,因此也将按您期望的那样返回1
和6
:
let x = 0;
async function test() {
// Enter asynchrony
await 0;
x += 5;
console.log('x :', x);
}
test();
x += 1;
console.log('x :', x);
但是,您的情况要复杂一些。
您已将await
表达式放入,并使用+=
。
您可能知道,在JS x += y
中与相同x = (x + y)
。我将使用后一种形式来更好地理解:
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 = (0 + await 5);
...然后,它到达await
并停止。
函数调用后的代码开始运行,并修改的值x
,然后将其记录下来。
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);
return x;
},
set z(value) {
console.log('set x =', value);
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);
/* Output:
inside async function
get x : 0 <-- async fn reads
main script
get x : 0
set x = 1
x : 1
set x = 5 <-- async fn writes
x : 5 <-- async fn logs
*/
/* Just to make console fill the available space */
.as-console-wrapper {
max-height: 100% !important;
}
x += y
与相同x = (x + y)
。” -并非每种语言的每种情况都如此,但总的来说,您可以指望它们表现相同。
此代码非常复杂,因为它需要一些来回的异步跳跃。让我们研究(接近)它实际上将如何执行,然后我将解释原因。我还更改了控制台日志以添加数字-使引用它们更加容易,并且还更好地显示了记录内容:
let x = 0; // 1 declaring and assigning x
async function test() { // 2 function declaration
x += await 5; // 4/7 assigning x
console.log('x1 :', x); // 8 printing
}
test(); // 3 invoking the function
x += 1; // 5 assigning x
console.log('x2 :', x); // 6 printing
因此,可以肯定的是,代码实际上并不是直接进行的。我们也有一个奇怪的4/7
事情。这实际上是这里问题的全部。
首先,让我们澄清-异步函数没有真正严格asynchronious。如果使用了await
关键字,它们只会暂停执行并在以后继续执行。没有它,它们将自上而下地同步执行表达式之后的表达式:
async function foo() {
console.log("--one");
console.log("--two");
}
console.log("start");
foo();
console.log("end");
async function foo() {
console.log("--one");
await 0; //just satisfy await with an expression
console.log("--two");
}
console.log("start");
foo();
console.log("end");
因此,我们首先需要知道,使用await
将使其余功能稍后执行。在给定的示例中,这意味着console.log('x1 :', x)
将在之后执行在其余同步代码。这是因为在当前事件循环完成后,所有Promises都将得到解决。
因此,这解释了为什么我们首先x2 : 1
登录,为什么第二次登录,而不是为什么后一个值为何。逻辑上x2 : 5
5
x += await 5
应该是5
...但是这是await
关键字的第二个陷阱-它会暂停函数的执行,但会在函数运行之前暂停执行。x += await 5
实际上将以以下方式进行处理
x
。在执行时,那是0
。await
下一个表达式是5
。因此,功能现在暂停,稍后将恢复。0 + 5
x
因此,功能暂停后,阅读x
是0
和恢复时,它已经改变了,但是,它不重读的价值x
。
如果我们将展开await
为Promise
执行的等效项,则您将:
let x = 0; // 1 declaring and assigning x
async function test() { // 2 function declaration
const temp = x; // 4 value read of x
await 0; //fake await to pause for demo
return new Promise((resolve) => {
x = temp + 5; // 7 assign to x
console.log('x1 :', x); // 8 printing
resolve();
});
}
test(); // 3 invoking the function
x += 1; // 5 assigning x
console.log('x2 :', x); // 6 printing
有点棘手,实际发生的事情是两个加法运算都在进行中,所以该运算就像:
在诺言之内:x += await 5
==> x = x + await 5
==> x = 0 + await 5
==>5
外:x += 1
==> x = x + 1
==> x = 0 + 1
==>1
由于所有上述操作都是从左到右进行的,因此可以同时计算加法的第一部分,并且由于在5之前有一个等待,所以加法可能会延迟一点。您可以通过在代码中放置断点来查看执行情况。
异步和等待是Promise的扩展。异步函数可以包含await表达式,该表达式暂停异步函数的执行并等待传递的Promise的分辨率,然后恢复异步函数的执行并返回解析的值。请记住,await关键字仅在异步函数内部有效。
即使您在调用测试函数后更改了x的值,但x的值仍将保持为0,因为异步函数已经创建了它的新实例。意味着在变量外部进行的所有更改在调用后都不会更改其内部的值。除非您将增量放在测试功能之上。
let x="Didn't receive change"; (async()=>{await 'Nothing'; console.log(x); await new Promise(resolve=>setTimeout(resolve,2000)); console.log(x)})(); x='Received synchronous change'; setTimeout(()=>{x='Received change'},1000)
它输出Received synchronous change
与Received change
await (x += 5)
和x += await 5
。