+ =の非同期関数


63

let x = 0;

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

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

xログに記録される値は1および5です。私の質問は次のとおりです:なぜx 52番目のログの値は何ですか?

が(非同期関数であるため)のtest後に実行された場合、x += 1xの値testは実行されるまでに1になるx += await 5ため、の値を作成する必要がありますx 6


1
との違いを知っている必要がawait (x += 5) ありx += await 5ます。
シンギジョン

回答:


60

TL; DR: 2番目のオペランド(右側)のキーワードが原因で、変更前に+=読み取り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ます。

最後に、インタプリタは追加を実行し、に格納5xてログに記録します。

この動作を確認するには、オブジェクトプロパティのゲッター/セッター内でログを記録します(この例で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)。」-これは、すべての言語のすべての状況に当てはまるわけではありませんが、一般に、それらが同じように動作することを期待できます。
Nick

11

あなたの声明x += await 5

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

_temporary値は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こともあります。そして、それが本当にここでの問題全体です。

まず、明確にしましょう-非同期関数は実際には厳密に非同期ではありません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ログに記録される理由と2番目にログに記録される理由を説明しますが、後者の値がである理由ではありません。論理的には...でなければなりませんが、これがキーワードの2番目のキャッチです。これは、関数の実行を一時停止しますが、すでに実行される前に何かを一時停止します。実際には次のように処理されますx2 : 55x += await 55awaitx += 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

Ya少しトリッキーなのは、実際に何が起こっているのかということです。両方の追加操作が並行して行われているため、操作は次のようになります。

promise内:x += await 5==> x = x + await 5==> x = 0 + await 5==>5

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

上記の操作はすべて左から右に行われるため、加算の最初の部分が同時に計算される可能性があります。また、additioが少し遅延する可能性がある5の前の待機があるためです。コード内にブレークポイントを置くことで実行を確認できます。


1

AsyncとAwaitはpromiseの拡張です。非同期関数には、非同期関数の実行を一時停止し、渡されたPromiseの解決を待機してから、非同期関数の実行を再開し、解決された値を返すawait式を含めることができます。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
1
弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.