Asynchrone Funktion mit + =


63

let x = 0;

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

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

Die Werte von xprotokolliert sind 1und 5. Meine Frage ist: Warum ist der Wert von x 5im zweiten Protokoll?

Wenn das testnach ausgeführt wird x += 1(da es sich um eine asynchrone Funktion handelt), ist der Wert von x zum Zeitpunkt der testAusführung 1, daher x += await 5sollte der Wert von angegeben werden x 6.


1
Sie müssen den Unterschied zwischen await (x += 5) und kennen x += await 5.
Singhi John

Antworten:


60

TL; DR: Weil zuvor +=gelesen x, aber geschrieben, nachdem es sich geändert hat, aufgrund des awaitSchlüsselworts in seinem zweiten Operanden (rechts).


asyncFunktionen werden synchron ausgeführt, wenn sie bis zur ersten awaitAnweisung aufgerufen werden .

Wenn Sie also entfernen await, verhält es sich wie eine normale Funktion (mit der Ausnahme, dass es immer noch ein Versprechen zurückgibt).

In diesem Fall erhalten Sie 5und 6in der Konsole:

let x = 0;

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

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

Der erste awaitVorgang stoppt die synchrone Ausführung, auch wenn sein Argument synchron verfügbar ist. Daher wird Folgendes zurückgegeben 1und 6wie erwartet:

let x = 0;

async function test() {
  // Enter asynchrony
  await 0;

  x += 5;
  console.log('x :', x);
}

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

Ihr Fall ist jedoch etwas komplizierter.

Sie haben awaiteinen Ausdruck eingefügt, der verwendet +=.

Sie wissen wahrscheinlich, dass in JS x += yidentisch ist mit x = (x + y). Ich werde das letztere Formular zum besseren Verständnis verwenden:

let x = 0;

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

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

Wenn der Dolmetscher diese Zeile erreicht ...

x = (x + await 5);

... es beginnt es zu bewerten und es dreht sich um ...

x = (0 + await 5);

... dann erreicht es das awaitund hört auf.

Der Code nach dem Funktionsaufruf wird ausgeführt, ändert den Wert von xund protokolliert ihn anschließend.

xist jetzt 1.

Nach dem Beenden des Hauptskripts kehrt der Interpreter zur angehaltenen testFunktion zurück und setzt die Auswertung dieser Zeile fort:

x = (0 + 5);

Und da der Wert von xbereits ersetzt ist, bleibt er bestehen 0.

Schließlich führt der Interpreter die Addition durch, speichert 5sie xund protokolliert sie.

Sie können dieses Verhalten überprüfen, indem Sie sich in einem Objekt-Eigenschafts-Getter / -Setter anmelden (in diesem Beispiel wird y.zder Wert von 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;
}


Vielleicht erwähnenswert: "Sie wissen wahrscheinlich, das x += yist identisch mit x = (x + y)." - Dies ist nicht in jeder Situation in jeder Sprache der Fall, aber im Allgemeinen können Sie sich darauf verlassen, dass sie gleich handeln.
Nick

11

Ihre Aussage x += await 5Desugars zu

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

Der _temporäre Wert ist 0, und wenn Sie sich xwährend des await(was Ihr Code tut) ändern, spielt es keine Rolle, er wird 5danach zugewiesen .


9

Dieser Code ist recht komplex zu befolgen, da einige unerwartete asynchrone Sprünge hin und her erforderlich sind. Lassen Sie uns untersuchen (in der Nähe), wie es tatsächlich ausgeführt wird, und ich werde anschließend erklären, warum. Ich habe auch die Konsolenprotokolle geändert, um eine Nummer hinzuzufügen. Dies erleichtert das Verweisen auf sie und zeigt auch besser, was protokolliert wird:

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

Der Code läuft also nicht gerade, das ist sicher. Und wir haben auch eine seltsame 4/7Sache. Und das ist wirklich die Gesamtheit des Problems hier.

Lassen Sie uns zunächst klarstellen, dass asynchrone Funktionen nicht streng asynchron sind. Sie würden die Ausführung nur anhalten und später fortsetzen, wenn das awaitSchlüsselwort verwendet wird. Ohne sie führen sie Ausdruck für Ausdruck synchron von oben nach unten aus:

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");

Also, das erste , was müssen wir wissen , dass die Verwendung von awaitmachen den Rest später der Funktion auszuführen. In dem gegebenen Beispiel, dass die Mittel , die console.log('x1 :', x)ausgeführt wird , nachdem der Rest des Synchroncodes. Dies liegt daran, dass alle Versprechen nach Abschluss der aktuellen Ereignisschleife aufgelöst werden.

Dies erklärt also, warum wir zuerstx2 : 1 protokolliert werden und warum zweitens protokolliert wird, aber nicht, warum der letztere Wert ist . Logischerweise sollte ... aber hier ist der zweite Haken zum Keyword - es wird unterbricht die Ausführung der Funktion , sondern etwas , bevor es schon gelaufen ist. wird tatsächlich auf folgende Weise verarbeitetx2 : 55x += await 55awaitx += await 5

  1. Holen Sie sich den Wert von x. Zum Zeitpunkt der Hinrichtung ist das so 0.
  2. awaitder nächste Ausdruck, der ist 5. Die Funktion wird jetzt angehalten und später fortgesetzt.
  3. Setzen Sie die Funktion fort. Der Ausdruck wird als 5 aufgelöst.
  4. Addiere den Wert von 1. und den Ausdruck von 2/3: 0 + 5
  5. Weisen Sie den Wert von 4. bis zu x

Also, lesen Sie die Funktion pausiert , nachdem es das xist 0und wird fortgesetzt , wenn ist es schon geändert, aber es nicht erneut zu lesen , den Wert x.

Wenn wir das awaitin das PromiseÄquivalent auspacken , das ausgeführt werden würde, haben Sie:

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

Ja, es ist ein bisschen schwierig, was tatsächlich passiert, wenn beide Additionsoperationen parallel ablaufen, so dass die Operation wie folgt aussehen würde:

Innerhalb des Versprechens: x += await 5==> x = x + await 5==> x = 0 + await 5==>5

Draußen: x += 1==> x = x + 1==> x = 0 + 1==>1

Da alle oben genannten Operationen von links nach rechts ausgeführt werden, kann der erste Teil der Addition gleichzeitig berechnet werden, und da vor 5 gewartet wird, kann sich die Addition etwas verzögern. Sie können die Ausführung sehen, indem Sie einen Haltepunkt in den Code einfügen.


1

Async und Await sind Erweiterungen von Versprechungen. Eine asynchrone Funktion kann einen Warteausdruck enthalten, der die Ausführung der asynchronen Funktion anhält und auf die Auflösung des übergebenen Versprechens wartet. Anschließend wird die Ausführung der asynchronen Funktion fortgesetzt und der aufgelöste Wert zurückgegeben. Denken Sie daran, dass das Schlüsselwort await nur in asynchronen Funktionen gültig ist.

Selbst wenn Sie den Wert von x nach dem Aufrufen der Testfunktion geändert haben, bleibt der Wert von x 0, da die asynchrone Funktion bereits ihre neue Instanz erstellt hat. Das heißt, alles, was sich an der Variablen außerhalb der Variablen ändert, ändert den Wert innerhalb der Variablen nach dem Aufruf nicht. Es sei denn, Sie setzen Ihr Inkrement über die Testfunktion.


"Das heißt, alles, was sich an der Variablen außerhalb ändert, ändert nicht den Wert innerhalb der Variablen, nachdem sie aufgerufen wurde ": Das ist nicht wahr. Asynchrone Funktionen tun erhalten variable Änderungen während ihrer Ausführung. Versuchen 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)Sie Received synchronous changeReceived change
einfach Folgendes
Durch die Nutzung unserer Website bestätigen Sie, dass Sie unsere Cookie-Richtlinie und Datenschutzrichtlinie gelesen und verstanden haben.
Licensed under cc by-sa 3.0 with attribution required.