web-dev-qa-db-fra.com

Pourquoi Async / Await fonctionne-t-il correctement lorsque la boucle est à l'intérieur de la fonction async et non l'inverse?

J'ai trois extraits qui bouclent trois fois pendant que awaiting sur un promise.

Dans le premier extrait, cela fonctionne comme prévu et la valeur de i est décrémentée avec chaque await.

let i = 3;

(async () => {
  while (i) {
    await Promise.resolve();
    console.log(i);
    i--;
  }
})();

Production:

3
2
1

Dans le second, la valeur de i est décrémentée en continu jusqu'à ce qu'elle atteigne zéro, puis tous les awaits sont exécutés.

let i = 3;

while (i) {
  (async () => {
    await Promise.resolve();
    console.log(i);
  })();
  i--;
}

Production:

0
0
0

Enfin, celui-ci provoque un Allocation failed - JavaScript heap out of memory erreur et n'imprime aucune valeur.

let i = 3;
while (i) {
  (async () => {
    await Promise.resolve();
    console.log(i);
    i--;
  })();
}

Quelqu'un peut-il expliquer pourquoi ils présentent ces différents comportements? Merci.

16
Ahmed Karaman

Concernant votre deuxième extrait:

Appeler une fonction asynchrone sans attendre son résultat s'appelle tirer et oublier. Vous dites à JavaScript qu'il doit démarrer un traitement asynchrone, mais vous ne vous souciez pas quand et comment il se termine. Voilà ce qui arrive. Il boucle, déclenche certaines tâches asynchrones, lorsque la boucle est terminée, elles se terminent parfois et enregistre 0 car la boucle a déjà atteint sa fin. Si vous le feriez:

await (async () => {
  await Promise.resolve();
  console.log(i);
})();

il bouclera dans l'ordre.

Concernant votre troisième extrait:

Vous ne décrémentez jamais i dans la boucle, la boucle s'exécute donc indéfiniment. Cela décrémenterait i si les tâches asynchrones étaient exécutées quelque part, mais cela ne se produit pas car la boucle while devient folle et bloque et plante le navigateur.

 let i = 3;
 while(i > 0) {
   doStuff();
 }
15
Jonas Wilms

Se concentrant principalement sur le dernier exemple:

let i = 3;
while (i) {
  (async () => {
    await Promise.resolve();
    console.log(i);
    i--;
  })();
}

Il peut être utile de réécrire le code sans asynchrone/attendre pour révéler ce qu'il fait réellement. Sous le capot, l'exécution de code de la fonction async est différée pour plus tard:

let callbacks = [];

let i = 0;
while (i > 0) {
  callbacks.Push(() => {
    console.log(i);
    i--;
  });
}

callbacks.forEach(cb => {
  cb();
});

Comme vous pouvez le voir, aucun des rappels n'est exécuté tant que la boucle n'est pas terminée. Étant donné que la boucle ne s'arrête jamais, le VM finira par manquer d'espace pour stocker les rappels.

9
Joel Cornett

Dans votre exemple particulier, il décrémente le i puis exécute le code async comme:

let i = 3;

while (i) {
  i--; // <---------------------
  (async () => {            // |
    await Promise.resolve();// |
    console.log(i);         // |
  })();                     // |
 // >---------------------------
}

En ce qui concerne votre troisième extrait de code, il ne diminuera jamais la valeur i et la boucle s'exécute donc indéfiniment et bloque donc l'application:

let i = 3;
while (i) {
  (async () => {
    await Promise.resolve(); // await and resolve >-----------
    // the following code doesn't run after it resolves   // |
    console.log(i);                                       // |
    i--;                                                  // |
  })();                                                   // |
  // out from the (async() => {})() <-------------------------
}
2
Bhojendra Rauniyar

Parce que dans le premier cas, console.log et décrément fonctionnent en synchronisation les uns avec les autres car ils sont tous deux à l'intérieur de la même fonction asynchrone. Dans le second cas, console.log fonctionne de manière asynchrone et décrémente de manière synchrone. Par conséquent, la décrémentation sera exécutée en premier, la fonction asynchrone attendra la fin de la fonction synchrone, puis elle sera exécutée avec i == 0

Dans le troisième cas, le corps de la boucle s'exécute de manière synchrone et exécute la fonction asynchrone à chaque itération. Par conséquent, la décrémentation ne peut pas fonctionner jusqu'à la fin de la boucle, donc la condition dans le cycle est toujours vraie. Et donc jusqu'à ce que la pile ou la mémoire soit pleine

2
Nikita Umnov