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?
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:
async
, ce qui signifie qu'elle retournera toujours une promesse.await
, il suspend l'exécution de cette fonction jusqu'à la résolution de la promesse attendue.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.await
est toujours suspendu, mais d'autres événements peuvent maintenant être traités.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.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..then()
attachés à la promesse que cette fonction était précédemment retournée pour être appelée..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.
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.
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
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);
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.