+ =的异步功能


63

let x = 0;

async function test() {
    x += await 5;
    console.log('x :', x);
}

test();
x += 1;
console.log('x :', x);

x记录的值为15。我的问题是:为什么x 5第二个日志的值是多少?

如果在test之后执行x += 1(因为它是异步函数),则x的值在test执行时为1 ,因此x += await 5应使的值为x 6


1
你必须知道的区别await (x += 5)x += await 5
Singhi John

Answers:


60

TL; DR:由于其第二个操作数(右侧)中的关键字,因此在更改之前先+=读取,x然后写入await


async函数在调用直到第一条await语句时会同步运行。

因此,如果删除await,它的行为就像普通函数一样(除了它仍然返回Promise)。

在这种情况下,您将在控制台中获得56

let x = 0;

async function test() {
  x += 5;
  console.log('x :', x);
}

test();
x += 1;
console.log('x :', x);

第一个将await停止同步运行,即使其参数可同步使用,因此也将按您期望的那样返回16

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

最后,解释器进行加法,将其存储5x并记录下来。

您可以通过在对象属性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)。” -并非每种语言的每种情况都如此,但总的来说,您可以指望它们表现相同。
尼克

11

您的陈述x += await 5

const _temp = x;
await;
x = _temp + 5;

_temp的卵巢值是0,如果更改x过程中await(你的代码所做的)也没关系,它被分配5之后。


9

此代码非常复杂,因为它需要一些来回的异步跳跃。让我们研究(接近)它实际上将如何执行,然后我将解释原因。我还更改了控制台日志以添加数字-使引用它们更加容易,并且还更好地显示了记录内容:

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 : 55x += await 5应该是5...但是这是await关键字的第二个陷阱-它会暂停函数的执行,但会在函数运行之前暂停执行。x += await 5实际上将以以下方式进行处理

  1. 获取的值x。在执行时,那是0
  2. await下一个表达式是5。因此,功能现在暂停,稍后将恢复。
  3. 恢复功能。表达式解析为5。
  4. 将1中的值与2/3中的表达式相加: 0 + 5
  5. 将值从4.分配给 x

因此,功能暂停后,阅读x0和恢复时,它已经改变了,但是,它不重读的价值x

如果我们将展开awaitPromise执行的等效项,则您将:

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


3

有点棘手,实际发生的事情是两个加法运算都在进行中,所以该运算就像:

在诺言之内:x += await 5==> x = x + await 5==> x = 0 + await 5==>5

外:x += 1==> x = x + 1==> x = 0 + 1==>1

由于所有上述操作都是从左到右进行的,因此可以同时计算加法的第一部分,并且由于在5之前有一个等待,所以加法可能会延迟一点。您可以通过在代码中放置断点来查看执行情况。


1

异步和等待是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 changeReceived change
FZS
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.