118 votes

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

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

240voto

jfriend00 Points 152127

async/await de ne pas bloquer l'ensemble de l'interprète. node.js passe encore tous les Javascript en tant que mono-thread, et même si certains le code est en attente sur un async/await, d'autres événements peuvent continuer à exécuter leurs gestionnaires d'événements (donc node.js n'est pas bloqué). La file d'attente d'événements est toujours en cours de révision pour d'autres événements. En fait, ce sera un événement qui résout une promesse qui permettra à l' await d'arrêt en attente et exécutez 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");
});

Donc, await met juste le code suivant dans le champ d'application dans un invisible .then() gestionnaire et tout le reste fonctionne à peu près le même que si c'était écrit avec un .then() gestionnaire.

Donc, await vous permet d'enregistrer l'écriture de l' .then() gestionnaire et donne le code synchrone pour elle (même si elle n'est pas vraiment synchrone). À la fin c'est un raccourci qui vous permet d'écrire du code asynchrone avec moins de lignes de code. On doit se rappeler cependant que toute promesse qui peut rejeter doit avoir un try/catch quelque part autour de lui pour l'attraper et à manipuler que le rejet.

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

  1. Appel de fonction est faite
  2. L'interprète voit que la fonction est déclarée comme async ce qui signifie qu'il retournera toujours une promesse.
  3. L'interprète commence l'exécution de la fonction.
  4. Quand il rencontre un await mot-clé, il suspend l'exécution de cette fonction jusqu'à ce que la promesse qui est attendu résolu.
  5. La fonction retourne alors une promesse non résolus.
  6. À ce stade, l'interprète continue à exécuter tout ce qui vient après l'appel de fonction (généralement un fn().then() est suivi par d'autres lignes de code). L' .then() des gestionnaires ne sont pas encore été exécutée, parce que la promesse n'est pas encore résolu.
  7. À un certain point, cette séquence de Javascript termine et renvoie le contrôle de l'interprète.
  8. L'interprète est maintenant libre pour servir à d'autres événements de la file d'attente des événements. L'origine de l'appel de fonction qui a couru dans un await mot-clé est toujours suspendu, mais d'autres événements peuvent être traitées maintenant.
  9. Dans le futur, la promesse d'origine qui était attendu est résolu. Lorsqu'il est temps que de se faire traiter dans la file d'attente des événements, précédemment suspendu fonction continue l'exécution sur la ligne après l' await. Si il y a plus de await des déclarations, puis l'exécution de la fonction est à nouveau suspendu jusqu'à ce que cette promesse se résout.
  10. Finalement, la fonction de frappe return déclaration ou atteint la fin de la fonction du corps. Si il y a un return xxx instruction, puis l' xxx est évaluée et son résultat devient la valeur résolue de la promesse que ce async fonction a déjà retourné. La fonction est maintenant effectuée en cours d'exécution et à la promesse qu'il a précédemment retourné a été résolu.
  11. Ce sera la cause de tout .then() des gestionnaires attaché à la promesse que cette fonction déjà retournés à être appelé.
  12. Après ceux - .then() des gestionnaires d'exécution, le travail de ce async fonction est enfin terminé.

Ainsi, alors que l'ensemble de l'interprète n'a pas de bloc (d'autres événements Javascript peut encore être réparé), l'exécution de la spécificité de la async fonction qui contient l' await déclaration a été suspendu jusqu'à ce que la promesse qui était attendu résolu. Ce qui est important à comprendre, c'est l'étape 5 ci-dessus. Lors de la première await est atteint, la fonction retourne immédiatement une promesse non résolus et le code après cette fonction est exécutée (avant la promesse awaited est résolu). C'est pour cette raison que l'ensemble de l'interprète n'est pas bloqué. L'exécution se poursuit. Seulement à l'intérieur d'une fonction sont suspendus jusqu'à ce qu'une promesse est résolu.

30voto

trincot Points 10112

async/await est juste sucre syntaxique pour then des appels sur une promesse. Ni les Promesses, ni async ni await créer de nouveaux threads.

Lors de l' await est exécutée, l'expression qui suit, il est évalué de façon synchrone. Il devrait être une promesse, mais si elle ne l'est pas, il est enveloppé dans un, comme si vous aviez await Promise.resolve(expression).

Une fois que l'expression est évaluée, l' async fonction renvoie -- il retourne une promesse. Ensuite, le code de l'exécution se poursuit avec n'importe quel code en résulte que l'appel de fonction (même thread) jusqu'à ce que la pile des appels est vide.

À un certain point la promesse qui a été évalué pour l' await résoudre. Cela permettra de mettre un microtask dans le microtask file d'attente. Lorsque le moteur JavaScript a plus rien à faire dans la tâche en cours, il va consommer de l'événement suivant dans le microtask file d'attente. Que ce microtask implique une résolu promesse, il sera de restaurer le précédent état d'exécution de l' async de la fonction et de continuer avec tout ce qui vient après l' await.

La fonction peut exécuter d'autres await états, avec le même comportement, même si la fonction ne renvoie plus à l'endroit où il a été appelé à l'origine d' (comme cet appel a déjà été traité avec le premier await), il se contente de laisser la pile des appels vides, et laisse le moteur JavaScript pour le processus de la microtask et les files d'attente de tâches.

Tout ce qui se passe avec le même fil.

13voto

Nidhin David Points 80

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 un wrapper de niveau de langue autour de Promises afin que le code puisse avoir une "apparence" synchrone

10voto

mikep Points 956

Va async/await bloquer un thread node.js? Comme @Nidhin David dit cela dépend de ce code que vous avez à l'intérieur de fonction async - db appels, les appels réseau, le système de fichier d'appels ne sont pas de blocage mais les blocages sont, par exemple, de long pour le/tout en cycles, JSON stringify/parse et le mal/vulnérables expressions régulières (google pour les Rétablissements des Attaques).


Ce premier exemple de bloc principal nœud de fil comme prévu et aucune autre demande/les clients peuvent être servis.

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 il sera rapide. Ensuite, exécutez simplement une demande lente http://localhost:8080/test/ et aucune autre demande (même ceux rapide à http://localhost:8080/) ne sera pas servi jusqu'à lent (blocage) demande se termine.


Ce deuxième exemple utilise des promesses, mais il reste bloquer le principal nœud de fil et pas d'autres demandes/les clients peuvent être servis.

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

Cette troisième exemple utilise async+attendre, mais il est également de blocage (async+attendre est la même que natif de la Promesse).

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

Quatrième exemple utilise setTimeout() qui provoque un ralentissement demande semble être servi immédiatement (navigateur devient vite "Fait"), mais il est également de blocage et de toutes les autres demandes d'attendre jusqu'à ce que evilRegExp() se termine.

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

3voto

user1738579 Points 41

J'ai juste eu un "aha!" moment, et je croyais que j'avais le transmettre. "en attente" ne renvoie pas directement à JavaScript - il retourne le contrôle à l'appelant. Permettez-moi d'illustrer. Voici un programme en utilisant les 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 que nous voulons, c'est 1) les deux étapes sont immédiatement commencé, et 2) lorsqu'une étape est prêt à être manipulé (imaginez une requête Ajax, mais ici, nous avons juste à attendre pour une certaine période de temps), le traitement de chaque étape se produit immédiatement.

La "manipulation" code ici est de la console.log("l'étape X traitées"). Ce code (qui, dans une application réelle peut être assez long et peut-être inclure imbriquée attend), est un rappel, mais nous préférerions, pour être de premier niveau de code dans une fonction.

Ici est l'équivalent du code à l'aide async/await. Notez que nous avons eu à créer un sleep() de la fonction puisque nous avons besoin d'attendre sur 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()

L'important à emporter pour moi a été que les attendent dans l'étape 1. () retourne le contrôle au corps principal code de sorte que, étape 2() peut être appelée pour commencer cette étape, et l'attendent dans step2() renvoie également au corps principal code de sorte que "toutes les mesures ont commencé" peut être imprimé. Certaines personnes recommandent que vous utilisez "en attente de la Promesse.tous les()" pour démarrer plusieurs étapes, puis après ça, gérer toutes les étapes en utilisant les résultats (qui apparaît dans un tableau). Toutefois, lorsque vous le faites, aucune étape n'est traitée jusqu'à ce que toutes les étapes de résoudre. Ce n'est pas idéal, et semble être totalement inutile.

Prograide.com

Prograide est une communauté de développeurs qui cherche à élargir la connaissance de la programmation au-delà de l'anglais.
Pour cela nous avons les plus grands doutes résolus en français et vous pouvez aussi poser vos propres questions ou résoudre celles des autres.

Powered by:

X