Aujourd'hui, dans la sixiÚme partie de la traduction du manuel Node.js, nous parlerons de la boucle d'événements, de la pile d'appels, de la fonction
process.nextTick()
et des temporisateurs. La compréhension de ces mécanismes et d'autres mécanismes Node.js est l'une des pierres angulaires du développement d'applications réussi pour cette plate-forme.

[Nous vous conseillons de lire] Autres parties du cyclePartie 1:
Informations générales et mise en routePartie 2:
JavaScript, V8, quelques astuces de développementPartie 3:
Hébergement, REPL, travailler avec la console, les modulesPartie 4:
fichiers npm, package.json et package-lock.jsonPartie 5:
npm et npxPartie 6:
boucle d'événements, pile d'appels, temporisateursPartie 7:
Programmation asynchronePartie 8:
Guide Node.js, Partie 8: Protocoles HTTP et WebSocketPartie 9:
Guide Node.js, partie 9: utilisation du systĂšme de fichiersPartie 10:
Guide Node.js, Partie 10: Modules standard, flux, bases de données, NODE_ENVPDF complet du guide Node.js Boucle d'événement
Si vous voulez comprendre comment le code JavaScript est exécuté, la boucle d'événement est l'un des concepts les plus importants que vous devez comprendre. Ici, nous allons parler du fonctionnement de JavaScript en mode monothread et de la gestion des fonctions asynchrones.
Je dĂ©veloppe JavaScript depuis de nombreuses annĂ©es, mais je ne peux pas dire que j'ai complĂštement compris comment tout fonctionne, pour ainsi dire, "sous le capot". Le programmeur peut ne pas ĂȘtre au courant des subtilitĂ©s du dispositif des sous-systĂšmes internes de l'environnement dans lequel il travaille. Mais il est gĂ©nĂ©ralement utile d'avoir au moins une idĂ©e gĂ©nĂ©rale de ces choses.
Le code JavaScript que vous écrivez s'exécute en mode monothread. à un certain moment, une seule action est exécutée. Cette limitation est en fait trÚs utile. Cela simplifie considérablement le fonctionnement des programmes, éliminant ainsi la nécessité pour les programmeurs de résoudre des problÚmes spécifiques aux environnements multithreads.
En fait, un programmeur JS doit faire attention uniquement aux actions que son code effectue exactement et essayer d'éviter les situations qui provoquent le blocage du thread principal. Par exemple - passer des appels réseau en mode synchrone et des
cycles sans fin.
En rĂšgle gĂ©nĂ©rale, les navigateurs, dans chaque onglet ouvert, ont leur propre boucle d'Ă©vĂ©nements. Cela vous permet d'exĂ©cuter le code de chaque page dans un environnement isolĂ© et d'Ă©viter les situations oĂč une certaine page, dans le code dont il existe une boucle infinie ou des calculs lourds sont effectuĂ©s, est capable de «suspendre» l'intĂ©gralitĂ© du navigateur. Le navigateur prend en charge le travail de nombreuses boucles d'Ă©vĂ©nements simultanĂ©ment existantes, utilisĂ©es, par exemple, pour traiter les appels vers diverses API. De plus, une boucle d'Ă©vĂ©nement propriĂ©taire est utilisĂ©e pour prendre en charge
les travailleurs Web .
La chose la plus importante dont un programmeur JavaScript doit constamment se souvenir est que son code utilise sa propre boucle d'Ă©vĂ©nements, donc le code doit ĂȘtre Ă©crit pour que cette boucle d'Ă©vĂ©nements ne soit pas bloquĂ©e.
Verrou de boucle d'événement
Tout code JavaScript qui prend trop de temps Ă exĂ©cuter, c'est-Ă -dire un code qui ne prend pas le contrĂŽle de la boucle d'Ă©vĂ©nements trop longtemps, bloque l'exĂ©cution de tout autre code de page. Cela conduit mĂȘme Ă bloquer le traitement des Ă©vĂ©nements de l'interface utilisateur, ce qui se reflĂšte dans le fait que l'utilisateur ne peut pas interagir avec les Ă©lĂ©ments de la page et travailler normalement avec, par exemple, le dĂ©filement.
Presque tous les mĂ©canismes d'E / S JavaScript de base ne sont pas bloquants. Cela s'applique Ă la fois au navigateur et Ă Node.js. Parmi ces mĂ©canismes, par exemple, nous pouvons mentionner les outils pour effectuer des requĂȘtes rĂ©seau utilisĂ©s dans les environnements client et serveur, et les outils pour travailler avec les fichiers Node.js. Il existe des mĂ©thodes synchrones pour effectuer de telles opĂ©rations, mais elles ne sont utilisĂ©es que dans des cas particuliers. C'est pourquoi les rappels traditionnels et les mĂ©canismes plus rĂ©cents - promesses et la construction asynchrone / attendent - sont d'une grande importance dans JavaScript.
Pile d'appels
La pile d'appels JavaScript est basĂ©e sur le principe LIFO (Last In, First Out - Last In, First Out). La boucle d'Ă©vĂ©nements vĂ©rifie constamment la pile d'appels pour voir si elle a une fonction qui doit ĂȘtre exĂ©cutĂ©e. Si, lors de l'exĂ©cution du code, une fonction y est appelĂ©e, des informations la concernant sont ajoutĂ©es Ă la pile des appels et cette fonction est exĂ©cutĂ©e.
Si mĂȘme avant que vous n'Ă©tiez pas intĂ©ressĂ© par le concept de «pile d'appel», alors si vous avez rencontrĂ© des messages d'erreur qui incluent une trace de pile, vous imaginez dĂ©jĂ Ă quoi il ressemble. Ici, par exemple, ressemble Ă ceci dans un navigateur.
Message d'erreur du navigateurLe navigateur, lorsqu'une erreur se produit, rend compte de la séquence d'appels aux fonctions, dont les informations sont stockées dans la pile d'appels, ce qui vous permet de trouver la source de l'erreur et de comprendre quels appels à quelles fonctions ont conduit à la situation.
Maintenant que nous avons parlé de la boucle d'événements et de la pile d'appels en termes généraux, considérons un exemple qui illustre l'exécution d'un fragment de code et à quoi ressemble ce processus en termes de boucle d'événements et de pile d'appels.
Boucle d'événement et pile d'appels
Voici le code que nous expérimenterons:
const bar = () => console.log('bar') const baz = () => console.log('baz') const foo = () => { console.log('foo') bar() baz() } foo()
Si ce code est exécuté, les éléments suivants arriveront à la console:
foo bar baz
Un tel résultat est tout à fait attendu. A savoir, lorsque ce code est exécuté, la fonction
foo()
est d'abord appelée. à l'intérieur de cette fonction, nous appelons d'abord la fonction
bar()
, puis la fonction
baz()
. Dans le mĂȘme temps, la pile d'appels lors de l'exĂ©cution de ce code subit les modifications illustrĂ©es dans la figure suivante.
Modification de l'Ă©tat de la pile d'appels lors de l'exĂ©cution du code sous enquĂȘteLa boucle d'Ă©vĂ©nements, Ă chaque itĂ©ration, vĂ©rifie s'il y a quelque chose dans la pile des appels, et si c'est le cas, elle le fait jusqu'Ă ce que la pile des appels soit vide.
Itérations de boucle d'événementMise en file d'attente d'une fonction
L'exemple ci-dessus semble assez ordinaire, il n'a rien de spĂ©cial: JavaScript trouve le code qui doit ĂȘtre exĂ©cutĂ© et l'exĂ©cute dans l'ordre. Nous parlerons de la façon de diffĂ©rer l'exĂ©cution de la fonction jusqu'Ă ce que la pile d'appels soit effacĂ©e. Pour ce faire, la construction suivante est utilisĂ©e:
setTimeout(() => {}), 0)
Il vous permet d'exécuter la fonction passée à la fonction
setTimeout()
aprÚs l'exécution de toutes les autres fonctions appelées dans le code de programme.
Prenons un exemple:
const bar = () => console.log('bar') const baz = () => console.log('baz') const foo = () => { console.log('foo') setTimeout(bar, 0) baz() } foo()
Ce que ce code imprime peut sembler inattendu:
foo baz bar
Lorsque nous exécutons cet exemple, la fonction
foo()
est appelée en premier. Dans ce document, nous appelons
setTimeout()
, en passant cette fonction, comme premier argument,
bar
. En passant
0
comme deuxiĂšme argument, nous informons le systĂšme que cette fonction doit ĂȘtre exĂ©cutĂ©e le plus tĂŽt possible. Ensuite, nous appelons la fonction
baz()
.
Voici Ă quoi ressemblera la pile d'appels.
Modification de l'état de la pile d'appels lors de l'exécution du codeVoici l'ordre dans lequel les fonctions de notre programme seront maintenant exécutées.
Itérations de boucle d'événementPourquoi cela se passe-t-il ainsi?
File d'attente des événements
Lorsque la fonction
setTimeout()
est appelĂ©e, le navigateur ou la plateforme Node.js dĂ©marre une minuterie. Une fois que la minuterie a fonctionnĂ© (dans notre cas, cela se produit immĂ©diatement, puisque nous l'avons dĂ©fini sur 0), la fonction de rappel passĂ©e Ă
setTimeout()
entre dans la file d'attente d'événements.
La file d'attente des événements, en ce qui concerne le navigateur, comprend les événements déclenchés par l'utilisateur - événements provoqués par des clics de souris sur les éléments de la page, événements qui sont déclenchés lorsque les données sont entrées à partir du clavier. Les gestionnaires d'
onload
DOM comme
onload
, les fonctions appelées lors de la réception de réponses aux demandes asynchrones de chargement de données, sont immédiatement là . Ici, ils attendent leur tour de traiter.
La boucle d'événements donne la priorité à ce qui se trouve dans la pile des appels. Tout d'abord, il fait tout ce qu'il parvient à trouver sur la pile, et une fois la pile vide, il procÚde au traitement de ce qui se trouve dans la file d'attente des événements.
Nous n'avons pas besoin d'attendre qu'une fonction comme
setTimeout()
finisse de fonctionner, car des fonctions similaires sont fournies par le navigateur et utilisent leurs propres flux. Ainsi, par exemple, en définissant le minuteur sur 2 secondes à l'aide de la fonction
setTimeout()
, vous ne devriez pas, aprĂšs avoir arrĂȘtĂ© l'exĂ©cution d'un autre code, attendre ces 2 secondes, car le minuteur fonctionne en dehors de votre code.
ES6 Job Queue
ECMAScript 2015 (ES6) a introduit le concept de Job Queue, qui est utilisĂ© par les promesses (elles sont Ă©galement apparues dans ES6). GrĂące Ă la file d'attente des travaux, le rĂ©sultat de l'exĂ©cution de la fonction asynchrone peut ĂȘtre utilisĂ© le plus rapidement possible, sans avoir Ă attendre que la pile d'appels soit effacĂ©e.
Si une promesse est résolue avant la fin de la fonction en cours, le code correspondant sera exécuté immédiatement aprÚs la fin de la fonction en cours.
J'ai trouvĂ© une analogie intĂ©ressante pour ce dont nous parlons. Cela peut ĂȘtre comparĂ© Ă des montagnes russes dans un parc d'attractions. Une fois que vous avez parcouru la colline et que vous voulez recommencer, vous prenez un billet et montez en queue de file. Voici comment fonctionne la file d'attente des Ă©vĂ©nements. Mais la file d'attente des travaux est diffĂ©rente. Ce concept est similaire Ă un billet Ă prix rĂ©duit, qui vous donne le droit de faire le prochain voyage immĂ©diatement aprĂšs avoir terminĂ© le prĂ©cĂ©dent.
Prenons l'exemple suivant:
const bar = () => console.log('bar') const baz = () => console.log('baz') const foo = () => { console.log('foo') setTimeout(bar, 0) new Promise((resolve, reject) => resolve('should be right after baz, before bar') ).then(resolve => console.log(resolve)) baz() } foo()
Voici ce qui sera sorti aprÚs son exécution:
foo baz should be right after baz, before bar bar
Ce que vous pouvez voir ici montre une sérieuse différence entre les promesses (et la construction async / wait, qui est basée sur elles) et les fonctions asynchrones traditionnelles, dont l'exécution est organisée à l'aide de
setTimeout()
ou d'autres API de la plate-forme utilisée.
process.nextTick ()
La méthode
process.nextTick()
interagit avec la boucle d'événements d'une maniÚre spéciale. Une tique est un seul cycle complet d'événements. En passant la fonction à la méthode
process.nextTick()
, nous informons le systĂšme que cette fonction doit ĂȘtre appelĂ©e une fois l'itĂ©ration en cours de la boucle d'Ă©vĂ©nements terminĂ©e, avant le dĂ©but de la suivante. L'utilisation de cette mĂ©thode ressemble Ă ceci:
process.nextTick(() => {
Supposons qu'une boucle d'Ă©vĂ©nement soit occupĂ©e Ă exĂ©cuter du code pour la fonction actuelle. Une fois cette opĂ©ration terminĂ©e, le moteur JavaScript exĂ©cutera toutes les fonctions passĂ©es Ă
process.nextTick()
lors de l'opération précédente. En utilisant ce mécanisme, nous nous efforçons de nous assurer qu'une certaine fonction est exécutée de maniÚre asynchrone (aprÚs la fonction actuelle), mais dÚs que possible, sans la placer dans la file d'attente.
Par exemple, si vous utilisez la construction
setTimeout(() => {}, 0)
, la fonction sera exécutée à la prochaine itération de la boucle d'événements, c'est-à -dire bien plus tard que lors de l'utilisation de
process.nextTick()
dans la mĂȘme situation. Cette mĂ©thode doit ĂȘtre utilisĂ©e lorsqu'il est nĂ©cessaire de garantir l'exĂ©cution de code au tout dĂ©but de la prochaine itĂ©ration de la boucle d'Ă©vĂ©nements.
setImmediate ()
Une autre fonction fournie par Node.js pour l'exécution de code asynchrone est
setImmediate()
. Voici comment l'utiliser:
setImmediate(() => {
La fonction de rappel passĂ©e Ă
setImmediate()
sera exécutée à la prochaine itération de la boucle d'événement.
En quoi
setImmediate()
différent de
setTimeout(() => {}, 0)
(c'est-Ă -dire d'un minuteur qui devrait fonctionner dĂšs que possible) et de
process.nextTick()
?
La fonction passĂ©e Ă
process.nextTick()
s'exécutera une fois l'itération en cours de la boucle d'événements terminée. Autrement dit, une telle fonction sera toujours exécutée avant la fonction dont l'exécution est planifiée à l'aide de
setTimeout()
ou
setImmediate()
.
L'appel de la fonction
setTimeout()
avec un délai défini de 0 ms est trÚs similaire à l'appel de
setImmediate()
. L'ordre d'exécution des fonctions qui leur sont transférées dépend de divers facteurs, mais dans les deux cas, des rappels seront appelés à la prochaine itération de la boucle d'événements.
Minuteries
Nous avons déjà parlé de la fonction
setTimeout()
, qui vous permet de planifier des appels aux rappels qui lui sont passés. Prenons un peu de temps pour décrire plus en détail ses fonctionnalités et considérons une autre fonction,
setInterval()
, similaire Ă celle-ci. Dans Node.js, les fonctions de travail avec les temporisateurs sont incluses dans le module
temporisateur , mais vous pouvez les utiliser sans connecter ce module dans le code, car elles sont globales.
â fonction setTimeout ()
Rappelez-vous que lorsque vous appelez la fonction
setTimeout()
, elle reçoit un rappel et l'heure, en millisecondes, aprÚs laquelle le rappel sera appelé. Prenons un exemple:
setTimeout(() => { // 2 }, 2000) setTimeout(() => { // 50 }, 50)
Ici, nous passons
setTimeout()
nouvelle fonction qui est immédiatement décrite, mais ici nous pouvons utiliser la fonction existante en passant
setTimeout()
son nom et un ensemble de paramÚtres pour l'exécuter. Cela ressemble à ceci:
const myFunction = (firstParam, secondParam) => {
La fonction
setTimeout()
renvoie un identifiant de temporisateur. Habituellement, il n'est pas utilisé, mais vous pouvez l'enregistrer et, si nécessaire, supprimer le minuteur si le rappel planifié n'est plus nécessaire:
const id = setTimeout(() => {
â ZĂ©ro retard
Dans les sections précédentes, nous avons utilisé
setTimeout()
, en le passant, comme le temps aprÚs lequel il est nécessaire d'appeler le rappel,
0
. Cela signifiait que le rappel serait appelé dÚs que possible, mais aprÚs l'achÚvement de la fonction actuelle:
setTimeout(() => { console.log('after ') }, 0) console.log(' before ')
Un tel code affichera les éléments suivants:
before after
Cette technique est particuliĂšrement utile dans les situations oĂč, lors de l'exĂ©cution de tĂąches de calcul lourdes, je ne voudrais pas bloquer le thread principal, permettant Ă d'autres fonctions d'ĂȘtre exĂ©cutĂ©es, divisant ces tĂąches en plusieurs Ă©tapes, exĂ©cutĂ©es en tant
setTimeout()
.
Si nous rappelons la fonction
setImmediate()
ci-dessus, alors elle est standard dans Node.js, ce qui ne peut pas ĂȘtre dit Ă propos des navigateurs (elle est
implémentée dans IE et Edge, mais pas dans d'autres).
â fonction setInterval ()
La fonction
setInterval()
est similaire Ă
setTimeout()
, mais il existe des différences entre elles. Au lieu d'exécuter le rappel qui lui est passé une fois,
setInterval()
appellera pĂ©riodiquement, avec l'intervalle spĂ©cifiĂ©, ce rappel. Cela continuera, idĂ©alement, jusqu'au moment oĂč le programmeur arrĂȘtera explicitement ce processus. Voici comment utiliser cette fonctionnalitĂ©:
setInterval(() => {
Un rappel passĂ© Ă la fonction ci-dessus sera appelĂ© toutes les 2 secondes. Afin de fournir la possibilitĂ© d'arrĂȘter ce processus, vous devez obtenir l'identifiant du temporisateur retournĂ© par
setInterval()
et utiliser la commande
clearInterval()
:
const id = setInterval(() => { // 2 }, 2000) clearInterval(id)
Une technique courante consiste Ă appeler
clearInterval()
Ă l'intĂ©rieur du rappel passĂ© Ă
setInterval()
lorsqu'une certaine condition est remplie. Par exemple, le code suivant sera exécuté périodiquement jusqu'à ce que la propriété
App.somethingIWait
soit
App.somethingIWait
sur
arrived
:
const interval = setInterval(function() { if (App.somethingIWait === 'arrived') { clearInterval(interval) // - , - } }, 100)
â RĂ©glage rĂ©cursif setTimeout ()
La fonction
setInterval()
appellera le rappel qui lui est passé toutes les
n
millisecondes, sans se soucier de savoir si ce rappel s'est terminé aprÚs son appel précédent.
Si chaque appel Ă ce rappel nĂ©cessite toujours le mĂȘme temps infĂ©rieur Ă
n
, aucun problĂšme ne se pose ici.
AppelĂ© pĂ©riodiquement, chaque session d'exĂ©cution prend le mĂȘme temps, se situant dans l'intervalle entre les appelsPeut-ĂȘtre que cela prend un temps diffĂ©rent pour terminer un rappel, qui est toujours infĂ©rieur Ă
n
. Si, par exemple, nous parlons d'effectuer certaines opérations de réseau, alors cette situation est tout à fait attendue.
Appelé périodiquement, chaque session d'exécution prend un temps différent, se situant entre les appelsLorsque vous utilisez
setInterval()
, une situation peut se produire lorsque le rappel prend plus de
n
, ce qui conduit à l'appel suivant avant la fin de l'appel précédent.
Appelé périodiquement, chaque session prend un temps différent, qui parfois ne correspond pas à l'intervalle entre les appelsAfin d'éviter cette situation, vous pouvez utiliser la technique de réglage de minuterie récursive en utilisant
setTimeout()
. Le fait est que le prochain rappel est prévu aprÚs la fin de son précédent appel:
const myFunction = () => {
Avec cette approche, le scĂ©nario suivant peut ĂȘtre implĂ©mentĂ©:
Un appel récursif à setTimeout () pour planifier l'exécution du rappelRésumé
Aujourd'hui, nous avons parlé des mécanismes internes de Node.js, tels que la boucle d'événements, la pile d'appels, et discuté du travail avec des temporisateurs qui vous permettent de planifier l'exécution de code. La prochaine fois, nous aborderons le sujet de la programmation asynchrone.
Chers lecteurs! Avez-vous rencontrĂ© des situations oĂč vous avez dĂ» utiliser process.nextTick ()?