Fonction asynchrone avec + =


63

let x = 0;

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

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

Les valeurs xenregistrées sont 1et 5. Ma question est: pourquoi la valeur de x 5sur le deuxième journal?

Si le testest exécuté après x += 1(puisqu'il s'agit d'une fonction asynchrone), alors la valeur de x est 1 au moment de l' testexécution, donc x += await 5devrait prendre la valeur de x 6.


1
Vous devez connaître la différence entre await (x += 5) et x += await 5.
Singhi John

Réponses:


60

TL; DR: Parce que +=lit xavant, mais l'écrit après qu'il a changé, en raison du awaitmot - clé dans son deuxième opérande (côté droit).


asyncles fonctions s'exécutent de manière synchrone lors de leur appel jusqu'à la première awaitinstruction.

Donc, si vous supprimez await, il se comporte comme une fonction normale (à l'exception qu'il renvoie toujours une promesse).

Dans ce cas, vous obtenez 5et 6dans la console:

let x = 0;

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

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

Le premier awaitarrête l'exécution synchrone, même si son argument est disponible de manière synchrone, donc ce qui suit reviendra 1et 6, comme vous vous y attendez:

let x = 0;

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

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

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

Cependant, votre cas est un peu plus compliqué.

Vous avez mis à l' awaitintérieur une expression, qui utilise +=.

Vous savez probablement que JS x += yest identique à x = (x + y). Je vais utiliser ce dernier formulaire pour une meilleure compréhension:

let x = 0;

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

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

Lorsque l'interprète atteint cette ligne ...

x = (x + await 5);

... il commence à l'évaluer, et il se transforme en ...

x = (0 + await 5);

... puis, il atteint le awaitet s'arrête.

Le code après l'appel de fonction commence à s'exécuter et modifie la valeur de x, puis l'enregistre.

xest maintenant 1.

Ensuite, après la fin du script principal, l'interpréteur revient à la testfonction suspendue et continue d'évaluer cette ligne:

x = (0 + 5);

Et, puisque la valeur de xest déjà substituée, elle reste 0.

Enfin, l'interprète fait l'ajout, les magasins 5à x, et l' enregistre.

Vous pouvez vérifier ce comportement en vous connectant à l'intérieur d'un getter / setter de propriété d'objet (dans cet exemple, y.zreflète la valeur de 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;
}


Peut-être intéressant de noter: "Vous savez probablement, c'est x += yidentique à x = (x + y)." - Ce n'est pas le cas dans toutes les situations dans toutes les langues, mais en général, vous pouvez compter sur eux agissant de la même manière.
Nick

11

Votre déclaration x += await 5desugars à

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

La _tempvaleur orale est 0, et si vous changez xpendant await(ce que fait votre code), cela n'a pas d'importance, elle est attribuée 5par la suite.


9

Ce code est assez complexe à suivre car il nécessite des sauts asynchrones inattendus d'avant en arrière. Examinons (de près) comment cela va réellement être exécuté et j'expliquerai pourquoi par la suite. J'ai également modifié les journaux de la console pour ajouter un nombre - facilite la référence à ceux-ci et montre également mieux ce qui est enregistré:

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

Donc, le code ne se déroule pas de manière directe, c'est sûr. Et nous avons aussi une 4/7chose étrange . Et c'est vraiment tout le problème ici.

Tout d'abord, clarifions - les fonctions asynchrones ne sont pas réellement strictement asynchrones. Ils ne suspendent l'exécution et ne reprennent plus tard que si le awaitmot clé est utilisé. Sans cela, ils exécutent de haut en bas, expression après expression de manière synchrone:

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

Donc, la première chose que nous devons savoir, c'est que l'utilisation awaitfera exécuter le reste de la fonction plus tard. Dans l'exemple donné, cela signifie que console.log('x1 :', x)va être exécuté après le reste du code synchrone. En effet, toutes les promesses seront résolues après la fin de la boucle d'événements en cours.

Donc, cela explique pourquoi nous nous x2 : 1connectons en premier et pourquoi x2 : 5est enregistré en deuxième mais pas pourquoi cette dernière valeur est 5. Logiquement x += await 5devrait être 5... mais voici le deuxième hic au awaitmot-clé - il suspendra l'exécution de la fonction mais n'importe quoi avant qu'il ne soit déjà exécuté. x += await 5va en fait être traité de la manière suivante

  1. Récupère la valeur de x. Au moment de l'exécution, c'est 0.
  2. awaitl'expression suivante qui est 5. Ainsi, la fonction s'arrête maintenant et sera reprise plus tard.
  3. Reprenez la fonction. L'expression est résolue comme 5.
  4. Ajoutez la valeur de 1. et l'expression de 2/3: 0 + 5
  5. Attribuez la valeur de 4. à x

Ainsi, la fonction fait une pause après avoir lu ce qui xest 0et reprend lorsqu'elle est déjà modifiée, mais elle ne relit pas la valeur de x.

Si nous déballons le awaitdans l' Promiseéquivalent qui s'exécuterait, vous avez:

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

Il est un peu difficile de savoir ce qui se passe réellement, c'est que les deux opérations d'addition se déroulent en parallèle, de sorte que l'opération serait comme:

Dans les limites de la promesse: x += await 5==> x = x + await 5==> x = 0 + await 5==>5

À l'extérieur: x += 1==> x = x + 1==> x = 0 + 1==>1

étant donné que toutes les opérations ci-dessus se déroulent de gauche à droite, la première partie de l'addition peut être calculée en même temps et puisqu'il y a une attente avant 5, l'addition peut retarder un peu. Vous pouvez voir l'exécution en plaçant un point d'arrêt dans le code.


1

Async et Await sont des extensions de promesses. Une fonction asynchrone peut contenir une expression d'attente qui suspend l'exécution de la fonction asynchrone et attend la résolution de Promise passée, puis reprend l'exécution de la fonction asynchrone et renvoie la valeur résolue. N'oubliez pas que le mot-clé wait n'est valide que dans les fonctions asynchrones.

Même si vous avez modifié la valeur de x après avoir appelé la fonction de test, la valeur de x restera toujours 0 car la fonction asynchrone a déjà créé sa nouvelle instance. Ce qui signifie que tout change sur la variable à l'extérieur de celle-ci ne changera pas la valeur à l'intérieur de celle-ci après son appel. Sauf si vous placez votre incrément au-dessus de la fonction de test.


" Ce qui signifie que tout change sur la variable à l'extérieur de celle-ci ne changera pas la valeur à l'intérieur de celle-ci après son appel ": ce n'est pas vrai. Fonctions Async ne reçoivent des changements variables au cours de leur exécution. Essayez juste ceci: 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)Il sort Received synchronous changeetReceived change
FZs le
En utilisant notre site, vous reconnaissez avoir lu et compris notre politique liée aux cookies et notre politique de confidentialité.
Licensed under cc by-sa 3.0 with attribution required.