web-dev-qa-db-fra.com

Est-ce que async / wait bloquera un thread node.js

Quand async/await utilisé dans une fonction node.js, bloquera-t-il le thread node.js jusqu'à l'exécution de la ligne de code suivante?

39
rajesh_pudota

async/await Ne bloque pas tout l'interprète. node.js exécute toujours tout le Javascript comme un seul thread et même si du code est en attente sur un async/await, d'autres événements peuvent toujours exécuter leurs gestionnaires d'événement (donc node.js n'est pas bloqué). La file d'attente d'événements est toujours en cours de traitement pour d'autres événements. En fait, ce sera un événement qui résout une promesse qui permettra au await de ne plus attendre et d’exécuter le code suivant.

Code comme ceci:

await foo();            // foo is an async function that returns a promise
console.log("hello");

est analogue à ceci:

foo().then(() => {
    console.log("hello");
});

Ainsi, await place simplement le code suivant dans cette portée dans un gestionnaire invisible .then() et tout le reste fonctionne à peu près de la même façon que s'il avait été écrit avec un .then() gestionnaire.

Ainsi, await vous permet de sauvegarder l'écriture du gestionnaire .then() et donne au code une apparence synchrone (même s'il n'est pas vraiment synchrone). En fin de compte, c'est un raccourci qui vous permet d'écrire du code asynchrone avec moins de lignes de code. Il faut toutefois garder à l’esprit que toute promesse qui peut être rejetée doit avoir un essai/attrape quelque part pour attraper et gérer ce rejet.

Logiquement, vous pouvez imaginer ce que fait node.js lorsqu'il rencontre un mot clé await lors de l'exécution d'une fonction comme suit:

  1. L'appel de fonction est fait
  2. L'interprète voit que la fonction est déclarée comme async, ce qui signifie qu'elle retournera toujours une promesse.
  3. L'interprète commence à exécuter la fonction.
  4. Lorsqu'il rencontre un mot clé await, il suspend l'exécution de cette fonction jusqu'à la résolution de la promesse attendue.
  5. La fonction renvoie alors une promesse non résolue.
  6. À ce stade, l'interpréteur continue d'exécuter tout ce qui survient après l'appel de la fonction (généralement, fn().then() est suivi d'autres lignes de code). Les gestionnaires .then() ne sont pas encore exécutés car la promesse n'est pas encore résolue.
  7. À un moment donné, cette séquence de Javascript se termine et rend le contrôle à l'interprète.
  8. L'interprète est maintenant libre de servir d'autres événements à partir de la file d'attente d'événements. L'appel de fonction d'origine ayant abouti à un mot clé await est toujours suspendu, mais d'autres événements peuvent maintenant être traités.
  9. À un moment donné, la promesse initiale qui était attendue est résolue. Quand il est temps que cela soit traité dans la file d'événements, la fonction précédemment suspendue continue de s'exécuter sur la ligne après le await. S'il existe d'autres instructions await, l'exécution de la fonction est à nouveau suspendue jusqu'à ce que la promesse soit résolue.
  10. Finalement, la fonction atteint une instruction return ou atteint la fin du corps de la fonction. S'il existe une instruction return xxx, Le xxx est évalué et son résultat devient la valeur résolue de la promesse que cette fonction async a déjà été renvoyée. L'exécution de la fonction est maintenant terminée et la promesse qu'elle a précédemment renvoyée a été résolue.
  11. Cela entraînera des gestionnaires .then() attachés à la promesse que cette fonction était précédemment retournée pour être appelée.
  12. Une fois que ces gestionnaires .then() ont été exécutés, le travail de cette fonction async est enfin terminé.

Ainsi, alors que tout l'interprète ne bloque pas (d'autres événements Javascript peuvent toujours être traités), l'exécution de la fonction spécifique async qui contient l'instruction await a été suspendue jusqu'à la promesse en cours. attendu résolu. Ce qui est important à comprendre, c'est l'étape 5 ci-dessus. Lorsque le premier await est atteint, la fonction retourne immédiatement une promesse non résolue et le code après l'exécution de cette fonction (avant que la promesse étant awaited ne soit résolue). C'est pour cette raison que tout l'interprète n'est pas bloqué. L'exécution continue. Seuls les éléments internes d'une fonction sont suspendus jusqu'à la résolution d'une promesse.

77
jfriend00

async/await N'est qu'un sucre syntaxique pour then appelle une promesse. Ni Promises, ni async ni await ne créent de nouveaux threads.

Lorsque await est exécuté, l'expression qui suit est évaluée de manière synchrone. Ce devrait être une promesse, mais si ce n’est pas le cas, il est emballé dans une seule, comme si vous aviez await Promise.resolve(expression).

Une fois cette expression évaluée, la fonction async retourne - elle retourne une promesse. Ensuite, l’exécution du code continue quel que soit le code qui suit cet appel de fonction (même thread) jusqu’à ce que la pile d’appel soit vide.

À un moment donné, la promesse évaluée pour le await sera résolue. Cela mettra une micro-tâche dans la file d'attente. Lorsque le moteur JavaScript n'a plus rien à faire dans la tâche en cours, il utilise l'événement suivant dans la file d'attente des microtaches. Comme cette micro-tâche implique une promesse résolue, elle restaure l'état d'exécution précédent de la fonction async et continue avec tout ce qui suit après le await.

La fonction peut exécuter d’autres instructions await avec un comportement similaire, bien que la fonction ne revienne plus à l’origine de son appel initial (cet appel ayant déjà été traité avec le premier await), il renvoie simplement en laissant la pile d'appels vide et laisse le moteur JavaScript pour traiter les files d'attente de microtaches et de tâches.

Tout cela se passe avec le même fil.

11
trincot

Tant que le code contenu dans async/wait est non bloquant, il ne bloquera pas, par exemple les appels de base de données, les appels réseau, les appels de système de fichiers.

Mais si le code contenu dans async/wait bloque, il bloquera tout le processus Node.js, par exemple des boucles infinies, des tâches gourmandes en temps processeur, telles que le traitement des images, etc.

Async/wait est essentiellement un encapsuleur de niveau de langue autour de Promises afin que le code puisse avoir une "apparence" synchrone

4
Nidhin David

Async/wait bloquera-t-il un thread node.js? Comme @Nidhin David a déclaré que cela dépendait du code que vous avez dans la fonction asynchrone - appels de base, appels réseau, appels de système de fichiers ne sont pas bloquants, mais ils sont par exemple longs pour les attaques ReDoS).


Ce premier exemple bloquera le thread de noeud principal comme prévu et aucune autre demande/client ne pourra être servi.

var http = require('http');

// This regexp takes to long (if your PC runs it fast, try to add some more "a" to the start of string).
// With each "a" added time to complete is always doubled.
// On my PC 27 times of "a" takes 2,5 seconds (when I enter 28 times "a" it takes 5 seconds).
// https://en.wikipedia.org/wiki/ReDoS
function evilRegExp() {
    var string = 'aaaaaaaaaaaaaaaaaaaaaaaaaaab';
    string.match(/^(a|a)+$/);
}

// Request to http://localhost:8080/ wil be served quickly - without evilRegExp() but request to
// http://localhost:8080/test/ will be slow and will also block any other fast request to http://localhost:8080/
http.createServer(function (req, res) {
    console.log("request", req.url);

    if (req.url.indexOf('test') != -1) {
      console.log('runing evilRegExp()');
      evilRegExp();
    }

    res.write('Done');
    res.end();
}).listen(8080);

Vous pouvez exécuter de nombreuses demandes parallèles à http: // localhost: 8080 / et ce sera rapide. Ensuite, exécutez une seule requête lente http: // localhost: 8080/test / et aucune autre requête (même ceux qui sont rapides à http: // localhost: 8080 / ) ne le fera pas. être servi jusqu'à ce que la demande lente (blocage) se termine.


Ce deuxième exemple utilise des promesses mais il bloque toujours le thread du nœud principal et aucune autre demande/client ne peut être servi.

var http = require('http');

function evilRegExp() {
    return new Promise(resolve => {
        var string = 'aaaaaaaaaaaaaaaaaaaaaaaaaaab';
        string.match(/^(a|a)+$/);
        resolve();
    });
}

http.createServer(function (req, res) {
      console.log("request", req.url);

    if (req.url.indexOf('test') != -1) {
      console.log('runing evilRegExp()');
      evilRegExp();
    }

    res.write('Done');
    res.end();

}).listen(8080);

Ce troisième exemple utilise async + wait mais il bloque également (async + wait est identique à Promise native).

var http = require('http');

async function evilRegExp() {
    var string = 'aaaaaaaaaaaaaaaaaaaaaaaaaaab';
    string.match(/^(a|a)+$/);
    resolve();
}

http.createServer(function (req, res) {
      console.log("request", req.url);

    if (req.url.indexOf('test') != -1) {
      console.log('runing evilRegExp()');
      await evilRegExp();
    }

    res.write('Done');
    res.end();

}).listen(8080);

Le quatrième exemple utilise setTimeout (), ce qui provoque une demande lente semble être servi immédiatement (le navigateur obtient rapidement "Terminé"), mais il bloque également et toute autre demande rapide attendra la fin de la procédure evilRegExp ().

var http = require('http');

function evilRegExp() {
    var string = 'aaaaaaaaaaaaaaaaaaaaaaaaaaab';
    string.match(/^(a|a)+$/);
}

http.createServer(function (req, res) {
      console.log("request", req.url);

    if (req.url.indexOf('test') != -1) {
      console.log('runing evilRegExp()');
      setTimeout(function() { evilRegExp(); }, 0);
    }

    res.write('Done');
    res.end();

}).listen(8080);
3
mikep

Je viens d'avoir un "aha!" moment et je pensais que je le transmettrais. "wait" ne renvoie pas le contrôle directement à JavaScript - il renvoie le contrôle à l'appelant. Laissez-moi illustrer. Voici un programme utilisant des rappels:

console.log("begin");
step1(() => console.log("step 1 handled"));
step2(() => console.log("step 2 handled"));
console.log("all steps started");

// ----------------------------------------------

function step1(func) {

console.log("starting step 1");
setTimeout(func, 10000);
} // step1()

// ----------------------------------------------

function step2(func) {

console.log("starting step 2");
setTimeout(func, 5000);
} // step2()

Le comportement souhaité est le suivant: 1) les deux étapes sont immédiatement lancées et 2) lorsqu'une étape est prête à être gérée (imaginez une demande Ajax, mais ici, nous attendons un certain temps), la gestion de chaque étape est immédiate. .

Le code de "manipulation" est ici console.log ("étape X traitée"). Ce code (qui dans une application réelle peut être assez long et éventuellement inclure des attentes imbriquées), est dans un rappel, mais nous préférerions qu'il s'agisse d'un code de niveau supérieur dans une fonction.

Voici un code équivalent utilisant async/wait. Notez que nous avons dû créer une fonction sleep () car nous devons attendre une fonction qui retourne une promesse:

let sleep = ms => new Promise((r, j)=>setTimeout(r, ms));

console.log("begin");
step1();
step2();
console.log("all steps started");

// ----------------------------------------------

async function step1() {

console.log("starting step 1");
await sleep(10000);
console.log("step 1 handled");
} // step1()

// ----------------------------------------------

async function step2() {

console.log("starting step 2");
await sleep(5000);
console.log("step 2 handled");
} // step2()

La solution importante pour moi était que wait dans step1 () renvoie le contrôle au code du corps principal de sorte que step2 () puisse être appelé pour démarrer cette étape et que wait dans step2 () retourne également au code du corps principal de sorte que " toutes les étapes commencées "peuvent être imprimées. Certaines personnes recommandent d'utiliser "attendre Promise.all ()" pour démarrer plusieurs étapes, puis de gérer toutes les étapes en utilisant les résultats (qui apparaîtront dans un tableau). Cependant, lorsque vous faites cela, aucune étape n'est traitée tant que toutes les étapes ne sont pas résolues. Ce n'est pas idéal et semble totalement inutile.

2
user1738579