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?
Réponses
Trop de publicités?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:
- Appel de fonction est faite
- L'interprète voit que la fonction est déclarée comme
async
ce qui signifie qu'il retournera toujours une promesse. - L'interprète commence l'exécution de la fonction.
- 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. - La fonction retourne alors une promesse non résolus.
- À 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. - À un certain point, cette séquence de Javascript termine et renvoie le contrôle de l'interprète.
- 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. - 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 deawait
des déclarations, puis l'exécution de la fonction est à nouveau suspendu jusqu'à ce que cette promesse se résout. - Finalement, la fonction de frappe
return
déclaration ou atteint la fin de la fonction du corps. Si il y a unreturn xxx
instruction, puis l'xxx
est évaluée et son résultat devient la valeur résolue de la promesse que ceasync
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. - Ce sera la cause de tout
.then()
des gestionnaires attaché à la promesse que cette fonction déjà retournés à être appelé. - Après ceux -
.then()
des gestionnaires d'exécution, le travail de ceasync
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.
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.
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
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);
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.