Comment démarrer rapidement avec VUE3.0 : découvrir
Bien que JavaScript soit monothread, la boucle d'événements utilise autant que possible le noyau du système, permettant à Node.js d'effectuer des opérations d'E/S non bloquantes. Bien que la plupart des noyaux modernes soient multithreads, ils peuvent gérer des tâches multithreads dans le système. arrière-plan. Lorsqu'une tâche est terminée, le noyau l'informe à Node.js, puis le rappel approprié sera ajouté à la boucle pour exécution. Cet article présentera ce sujet plus en détail.
Lorsque Node.js démarre l'exécution, la boucle d'événements. sera d'abord initialisé. , traitera le script d'entrée fourni (ou le placera dans le REPL, ce qui n'est pas couvert dans ce document). Cela effectuera un appel d'API asynchrone, planifiera une minuterie ou appellera process.nextTick(), puis commencez à traiter la boucle d'événements. La
figure suivante montre la séquence d'exécution de la boucle d'événements
. ┌─>│ minuteries │ │ └─────────────┬─────────────┘ │ ┌─────────────┴─────────────┐ │ │ rappels en attente │ │ └─────────────┬─────────────┘ │ ┌─────────────┴─────────────┐ │ │ inactif, préparez-vous │ │ └─────────────┬────────────┘ ┌────────── ──────┐ │ ┌─────────────┴────────────┐ │ entrant : │ │ │ sondage │<─────┤ connexions, │ │ └─────────────┬─────────────┘ │ données, etc. │ │ ┌─────────────┴────────────┐ └────────── ──────┘ │ │ vérifier │ │ └─────────────┬─────────────┘ │ ┌─────────────┴─────────────┐ └──┤ fermer les rappels │ └─────────────────────────┘Chaque
case représente une étape de la boucle d'événements.
Chaque étape a une exécution de rappel de file d'attente FIFO. , chaque étape est exécutée à sa manière. De manière générale, lorsque la boucle d'événements entre dans une étape, elle effectuera toutes les opérations de l'étape en cours et commencera à exécuter les rappels dans la file d'attente de l'étape en cours jusqu'à ce que la file d'attente soit complètement consommée ou exécutée. le maximum de données. Lorsque la file d'attente est épuisée ou atteint sa taille maximale, la boucle d'événements passe à la phase suivante.
MinuteriesDans chaque processus de la boucle d'événements, Node .js vérifie s'il attend des E/S asynchrones et un minuteur, et sinon, arrête
Un minuteur spécifie le point critique auquel un rappel sera exécuté, plutôt que l'heure souhaitée
.pour s'exécuter. Le minuteur s'exécutera dès que possible après le temps écoulé spécifié, cependant, la planification du système d'exploitation ou d'autres rappels peuvent retarder l'exécution.
Techniquement parlant, la phase d'interrogation détermine quand le rappel est exécuté.
Par exemple, vous définissez une minuterie pour qu'elle s'exécute après 100 ms, mais votre script lit un fichier de manière asynchrone et prend 95 ms
const fs = require('fs') ; fonction someAsyncOperation (rappel) { // Supposons que cela prenne 95 ms fs.readFile('/chemin/vers/fichier', rappel); } const timeoutScheduled = Date.now(); setTimeout(() => { const delay = Date.now() - timeoutScheduled ; console.log(`${delay}ms se sont écoulés depuis que j'étais programmé`); }, 100); // fait someAsyncOperation qui prend 95 ms pour se terminer someAsyncOperation(() => { const startCallback = Date.now(); // fait quelque chose qui prendra 10 ms... while (Date.now() - startCallback < 10) { // ne fait rien } });
Lorsque la boucle d'événements entre dans la phase d'interrogation, il y a une file d'attente vide (fs.readFile() n'est pas encore terminée), elle attendra donc les millisecondes restantes jusqu'à ce que le seuil de minuterie le plus rapide soit atteint après 95 ms, fs. .readFile() a terminé la lecture du fichier et prendra 10 ms pour s'ajouter à la phase d'interrogation et terminer l'exécution. Lorsque le rappel est terminé, il n'y a aucun rappel dans la file d'attente à exécuter et la boucle d'événements revient à la phase des minuteries. pour exécuter le rappel de la minuterie. Dans cet exemple, vous verrez que le timer est retardé de 105 ms avant l'exécution.
Pour éviter que la phase d'interrogation ne bloque la boucle d'événements, libuv (la bibliothèque en langage C qui implémente la boucle d'événements et tous les comportements asynchrones sur la plateforme) a également une phase d'interrogation. max arrêter d'interroger plus d'événements
Cette phase exécute des rappels pour certaines opérations système (telles que les types d'erreurs TCP). Par exemple, certains systèmes * nix souhaitent attendre qu'une erreur soit signalée si un socket TCP reçoit ECONNREFUSED lors de la tentative de connexion. Celui-ci sera mis en file d'attente pour exécution pendant la phase de rappel en attente.
La phase d'interrogation a deux fonctions principales
Lorsque la boucle d'événements entre dans la phase d'interrogation et qu'il n'y a pas de minuterie, les deux choses suivantes se produisent
Une fois la file d'attente d'interrogation vide, la boucle d'événements. détectera si le minuteur a expiré. Si tel est le cas, la boucle d'événements atteindra l'étape des minuteurs et exécutera la
à cette étape. Permet aux personnes d'exécuter des rappels immédiatement après la fin de la phase d'interrogation. Si la phase d'interrogation devient inactive et que le script a été mis en file d'attente à l'aide de setImmediate(), la boucle d'événements peut continuer jusqu'à la phase de vérification au lieu d'attendre.
setImmediate() est en fait un minuteur spécial qui s'exécute dans une phase distincte de la boucle d'événements. Il utilise une API libuv pour planifier l'exécution des rappels une fois la phase d'interrogation terminée.
Généralement, à mesure que le code s'exécute, la boucle d'événements atteint finalement la phase d'interrogation, où elle attend les connexions entrantes, les requêtes, etc. Cependant, si un rappel est planifié à l'aide de setImmediate() et que la phase d'interrogation devient inactive, elle se terminera et poursuivra la phase de vérification au lieu d'attendre l'événement d'interrogation.
Si un socket ou une opération est fermé soudainement (par exemple socket.destroy()), l'événement de fermeture sera envoyé à cette étape, sinon il sera envoyé via process.nextTick() setImmediate
setImmediate() et setTimeout() sont similaires, mais se comportent différemment selon le moment où ils sont appelés.
L'ordre dans lequel chaque rappel est exécuté dépend de l'ordre dans lequel ils sont appelés Dans cet environnement, si le même module est appelé en même temps, le temps sera limité par les performances du processus (cela sera également affecté par d'autres applications exécutées sur cette machine
par exemple
)., si nous n'exécutons pas le script suivant dans I/O , bien qu'il soit affecté par les performances du processus, il n'est pas possible de déterminer l'ordre d'exécution de ces deux timers :
// timeout_vs_immediate.js setTimeout(() => { console.log('timeout'); }, 0); setImmediate(() => { console.log('immédiat'); });
$ nœud timeout_vs_immediate.js temps mort immédiat $ nœud timeout_vs_immediate.js immédiat timeout
Cependant, si vous entrez dans la boucle d'E/S, le rappel immédiat sera toujours exécuté en premier
// timeout_vs_immediate.js const fs = require('fs'); fs.readFile(__filename, () => { setTimeout(() => { console.log('timeout'); }, 0); setImmediate(() => { console.log('immédiat'); }); });
$ nœud timeout_vs_immediate.js immédiat temps mort $ nœud timeout_vs_immediate.js immédiatL'avantage du
timeout
setImmediate par rapport à setTimeout est que setImmediate sera toujours exécuté en premier avant tout temporisateur dans les E/S, quel que soit le nombre de temporisateurs existants.
Bien que process.nextTick() fasse partie de l'API asynchrone, vous avez peut-être remarqué qu'il n'apparaît pas dans le diagramme. En effet, process.nextTick() ne fait pas partie de la technologie de boucle d'événements. l'opération en cours est exécutée Une fois terminé, nextTickQueue sera exécuté quelle que soit l'étape actuelle de la boucle d'événements. Ici, les opérations sont définies comme des transformations du gestionnaire C/C++ sous-jacent et gèrent le JavaScript qui doit être exécuté. Selon le diagramme, vous pouvez appeler process.nextTick() à tout moment. Tous les rappels passés à process.nextTick() seront exécutés avant que la boucle d'événements ne continue son exécution. Cela peut conduire à de mauvaises situations car cela vous permet d'appeler de manière récursive. . process.nextTick() "affame" vos E/S, ce qui empêche la boucle d'événements d'entrer dans la phase d'interrogation.
Pourquoi cette situation est-elle incluse dans Node.js ? Puisque la philosophie de conception de Node.js est qu'une API doit toujours être asynchrone même si ce n'est pas obligatoire, regardez l'extrait suivant
function apiCall(arg, callback) { if (type d'argument !== 'string') retourner le processus.nextTick( rappel, new TypeError('l'argument doit être une chaîne') ); }
L'extrait vérifie les paramètres et s'il est incorrect, il transmet l'erreur au rappel. L'API a été récemment mise à jour pour permettre de transmettre des paramètres à process.nextTick(), ce qui lui permet d'accepter tous les paramètres transmis après le rappel comme arguments du rappel, vous n'avez donc pas besoin d'imbriquer les fonctions.
Ce que nous faisons, c'est renvoyer l'erreur à l'utilisateur, mais seulement si nous autorisons l'exécution du reste du code de l'utilisateur. En utilisant process.nextTick(), nous garantissons que apiCall() exécute toujours son rappel après le reste du code utilisateur et avant d'autoriser la poursuite de la boucle d'événements. Pour y parvenir, la pile d'appels JS est autorisée à se dérouler, puis le rappel fourni est immédiatement exécuté, ce qui permet d'effectuer des appels récursifs à process.nextTick() sans toucher RangeError : taille maximale de la pile d'appels dépassée à partir de la v8.