+= 异步函数
Async function with +=
let x = 0;
async function test() {
x += await 5;
console.log('x :', x);
}
test();
x += 1;
console.log('x :', x);
记录的 x
的值为 1
和 5
。我的问题是:为什么 x
5
的值在第二个日志上?
如果 test
在 x += 1
之后执行(因为它是一个异步函数)那么 x
的值在 [=16= 时是 1
]被执行,所以x += await 5
应该使x
的值变成6
.
你的声明 x += await 5
脱糖到
const _temp = x;
const _gain = await 5;
x = _temp + _gain;
_temp
orary 值为 0
,如果您在 await
期间更改 x
(您的代码所做的)没关系,它会之后分配 5
。
是的,这有点棘手,实际发生的是两个加法运算都是并行发生的,所以运算就像:
承诺内:x += await 5
==> x = x + await 5
==> x = 0 + await 5
==> 5
外部:x += 1
==> x = x + 1
==> x = 0 + 1
==> 1
由于上述所有操作都是从左到右进行的,因此加法的第一部分可能会同时计算,并且由于在 5 之前有一个等待,加法可能会延迟一点。您可以通过在代码中放置断点来查看执行情况。
TL;DR: 因为+=
之前读的是x
,改了之后才写,由于await
关键字在其第二个操作数(右侧)中。
async
在第一个 await
语句被调用时同步运行 运行。
因此,如果您删除 await
,它的行为就像一个普通函数(除了它仍然 return 是一个 Promise)。
在这种情况下,您会在控制台中得到 5
(来自函数)和 6
(来自主脚本):
let x = 0;
async function test() {
x += 5;
console.log('x :', x);
}
test();
x += 1;
console.log('x :', x);
第一个 await
停止同步 运行ning,即使它的参数是一个已经解析的承诺(或者在这里,根本不是一个承诺 - 这些将被转换为已解决的承诺await
),所以下面将 return 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)
(除非 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
) 中的表达式,将其转换为已解决的承诺,并开始等待它。
函数调用后的代码从运行开始,修改x
的值(从0
变为1
),然后记录。
x
现在是 1
.
然后,在主脚本完成后,解释器返回到暂停的 test
函数,并继续计算该行,其中 await
不受影响,看起来像这样:
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')); //Log stacktrace using an Error object
return x;
},
set z(value) {
console.log('set x =', value);
console.log(new Error().stack.replace('Error', 'Stacktrace')); //Log stacktrace using an Error object
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')
/* Output:
inside async function
get x : 0 <-------------- async fn reads
Stacktrace
at Object.get z [as z] (https://stacksnippets.net/js:19:17)
at test (https://stacksnippets.net/js:31:3) <-- async fn is synchronous here
at https://stacksnippets.net/js:35:1 <--------- (main script is still in the stack)
main script
get x : 0
Stacktrace
at Object.get z [as z] (https://stacksnippets.net/js:19:17)
at https://stacksnippets.net/js:37:1
set x = 1
Stacktrace
at Object.set z [as z] (https://stacksnippets.net/js:24:17)
at https://stacksnippets.net/js:37:5
x : 1
end of main script
set x = 5 <-------------- async fn writes
Stacktrace
at Object.set z [as z] (https://stacksnippets.net/js:24:17)
at test (https://stacksnippets.net/js:31:7) <-- async fn is asynchronous (main script is no longer in the stack)
x : 5 <------------------ async fn logs
*/
/* Just to make console fill the available space */
.as-console-wrapper {
max-height: 100% !important;
}
这段代码很难理解,因为它需要一些意外的异步来回跳转。让我们检查(接近)它实际上是如何执行的,我会在之后解释原因。我还更改了控制台日志以添加一个数字 - 使引用它们更容易并且还可以更好地显示记录的内容:
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
事情。这就是问题的全部。
首先,让我们澄清一下 - 异步函数不是实际上是严格异步的。如果使用 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)
将在 其余同步代码之后执行 。这是因为任何 Promise 都将在当前事件循环结束后得到解决。
所以,这解释了为什么我们首先记录 x2 : 1
,然后记录 x2 : 5
,而不是为什么后一个值是 5
.逻辑上 x += await 5
应该是 5
...但是这里是 await
关键字的第二个陷阱 - 它会 暂停 函数的执行但是之前的任何内容都已经 运行。 x += await 5
其实是要按照下面的方式处理
- 获取
x
的值。执行的时候就是0
.
await
下一个表达式是5
。因此,功能现在暂停,稍后将恢复。
- 恢复功能。表达式解析为 5.
- 将 1 的值和 2/3 的表达式相加:
0 + 5
- 将值从 4. 分配给
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
Async 和 Await 是 promises 的扩展。异步函数可以包含一个 await 表达式,该表达式暂停异步函数的执行并等待传递的 Promise 的解析,然后恢复异步函数的执行和 returns 已解析的值。请记住,await 关键字仅在异步函数内部有效。
即使您在调用测试函数后更改了 x 的值,x 的值仍然会保持为 0,因为异步函数已经创建了它的新实例。这意味着变量外部的所有变化都不会在它被调用后改变它内部的值。除非你把增量放在测试函数之上。
let x = 0;
async function test() {
x += await 5;
console.log('x :', x);
}
test();
x += 1;
console.log('x :', x);
记录的 x
的值为 1
和 5
。我的问题是:为什么 x
5
的值在第二个日志上?
如果 test
在 x += 1
之后执行(因为它是一个异步函数)那么 x
的值在 [=16= 时是 1
]被执行,所以x += await 5
应该使x
的值变成6
.
你的声明 x += await 5
脱糖到
const _temp = x;
const _gain = await 5;
x = _temp + _gain;
_temp
orary 值为 0
,如果您在 await
期间更改 x
(您的代码所做的)没关系,它会之后分配 5
。
是的,这有点棘手,实际发生的是两个加法运算都是并行发生的,所以运算就像:
承诺内:x += await 5
==> x = x + await 5
==> x = 0 + await 5
==> 5
外部:x += 1
==> x = x + 1
==> x = 0 + 1
==> 1
由于上述所有操作都是从左到右进行的,因此加法的第一部分可能会同时计算,并且由于在 5 之前有一个等待,加法可能会延迟一点。您可以通过在代码中放置断点来查看执行情况。
TL;DR: 因为+=
之前读的是x
,改了之后才写,由于await
关键字在其第二个操作数(右侧)中。
async
在第一个 await
语句被调用时同步运行 运行。
因此,如果您删除 await
,它的行为就像一个普通函数(除了它仍然 return 是一个 Promise)。
在这种情况下,您会在控制台中得到 5
(来自函数)和 6
(来自主脚本):
let x = 0;
async function test() {
x += 5;
console.log('x :', x);
}
test();
x += 1;
console.log('x :', x);
第一个 await
停止同步 运行ning,即使它的参数是一个已经解析的承诺(或者在这里,根本不是一个承诺 - 这些将被转换为已解决的承诺await
),所以下面将 return 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)
(除非 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
) 中的表达式,将其转换为已解决的承诺,并开始等待它。
函数调用后的代码从运行开始,修改x
的值(从0
变为1
),然后记录。
x
现在是 1
.
然后,在主脚本完成后,解释器返回到暂停的 test
函数,并继续计算该行,其中 await
不受影响,看起来像这样:
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')); //Log stacktrace using an Error object
return x;
},
set z(value) {
console.log('set x =', value);
console.log(new Error().stack.replace('Error', 'Stacktrace')); //Log stacktrace using an Error object
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')
/* Output:
inside async function
get x : 0 <-------------- async fn reads
Stacktrace
at Object.get z [as z] (https://stacksnippets.net/js:19:17)
at test (https://stacksnippets.net/js:31:3) <-- async fn is synchronous here
at https://stacksnippets.net/js:35:1 <--------- (main script is still in the stack)
main script
get x : 0
Stacktrace
at Object.get z [as z] (https://stacksnippets.net/js:19:17)
at https://stacksnippets.net/js:37:1
set x = 1
Stacktrace
at Object.set z [as z] (https://stacksnippets.net/js:24:17)
at https://stacksnippets.net/js:37:5
x : 1
end of main script
set x = 5 <-------------- async fn writes
Stacktrace
at Object.set z [as z] (https://stacksnippets.net/js:24:17)
at test (https://stacksnippets.net/js:31:7) <-- async fn is asynchronous (main script is no longer in the stack)
x : 5 <------------------ async fn logs
*/
/* Just to make console fill the available space */
.as-console-wrapper {
max-height: 100% !important;
}
这段代码很难理解,因为它需要一些意外的异步来回跳转。让我们检查(接近)它实际上是如何执行的,我会在之后解释原因。我还更改了控制台日志以添加一个数字 - 使引用它们更容易并且还可以更好地显示记录的内容:
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
事情。这就是问题的全部。
首先,让我们澄清一下 - 异步函数不是实际上是严格异步的。如果使用 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)
将在 其余同步代码之后执行 。这是因为任何 Promise 都将在当前事件循环结束后得到解决。
所以,这解释了为什么我们首先记录 x2 : 1
,然后记录 x2 : 5
,而不是为什么后一个值是 5
.逻辑上 x += await 5
应该是 5
...但是这里是 await
关键字的第二个陷阱 - 它会 暂停 函数的执行但是之前的任何内容都已经 运行。 x += await 5
其实是要按照下面的方式处理
- 获取
x
的值。执行的时候就是0
. await
下一个表达式是5
。因此,功能现在暂停,稍后将恢复。- 恢复功能。表达式解析为 5.
- 将 1 的值和 2/3 的表达式相加:
0 + 5
- 将值从 4. 分配给
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
Async 和 Await 是 promises 的扩展。异步函数可以包含一个 await 表达式,该表达式暂停异步函数的执行并等待传递的 Promise 的解析,然后恢复异步函数的执行和 returns 已解析的值。请记住,await 关键字仅在异步函数内部有效。
即使您在调用测试函数后更改了 x 的值,x 的值仍然会保持为 0,因为异步函数已经创建了它的新实例。这意味着变量外部的所有变化都不会在它被调用后改变它内部的值。除非你把增量放在测试函数之上。