La boucle d'événements Node.js

La boucle d'événement est l'un des aspects les plus importants à comprendre sur Node

introduction

LeBoucle d'événementest l'un des aspects les plus importants à comprendre sur Node.

Pourquoi est-ce si important? Parce que cela explique comment Node peut être asynchrone et avoir des E / S non bloquantes, et donc cela explique essentiellement «l'application tueur» de Node, ce qui a fait ce succès.

Le code JavaScript Node.js s'exécute sur un seul thread. Il ne se passe qu'une seule chose à la fois.

Il s'agit d'une limitation qui est en fait très utile, car elle simplifie beaucoup la façon dont vous programmez sans vous soucier des problèmes de concurrence.

Vous devez juste faire attention à la façon dont vous écrivez votre code et éviter tout ce qui pourrait bloquer le thread, comme les appels réseau synchrones ou infiniboucles.

En général, dans la plupart des navigateurs, il existe une boucle d'événements pour chaque onglet du navigateur, pour isoler chaque processus et éviter une page Web avec des boucles infinies ou un traitement lourd pour bloquer tout votre navigateur.

L'environnement gère plusieurs boucles d'événements simultanées, pour gérer les appels d'API par exemple.Travailleurs Webs'exécutent également dans leur propre boucle d'événements.

Vous devez principalement vous inquiéter du fait quevotre codefonctionnera sur une seule boucle d'événement et écrira du code avec cette chose à l'esprit pour éviter de le bloquer.

Bloquer la boucle d'événements

Tout code JavaScript qui prend trop de temps pour renvoyer le contrôle à la boucle d'événements bloquera l'exécution de tout code JavaScript dans la page, bloquera même le thread d'interface utilisateur, et l'utilisateur ne pourra pas cliquer, faire défiler la page, etc.

Presque toutes les primitives d'E / S en JavaScript ne sont pas bloquantes. Requêtes réseau, opérations sur le système de fichiers, etc. Le blocage est l'exception, et c'est pourquoi JavaScript est tellement basé sur les rappels, et plus récemment surpromessesetasynchroniser / attendre.

La pile d'appels

La pile d'appels est une file d'attente LIFO (Last In, First Out).

La boucle d'événements vérifie en permanencepile d'appelspour voir si une fonction doit être exécutée.

Ce faisant, il ajoute tout appel de fonction qu'il trouve à la pile d'appels et exécute chacun dans l'ordre.

Vous connaissez la trace de la pile d'erreurs que vous connaissez peut-être, dans le débogueur ou dans la console du navigateur? Le navigateur recherche les noms des fonctions dans la pile d'appels pour vous informer de la fonction à l'origine de l'appel en cours:

Exception call stack

Une explication simple de la boucle d'événements

Prenons un exemple:

j'utilisefoo,baretbazcommenoms aléatoires. Entrez n'importe quel type de nom pour les remplacer.

const bar = () => console.log('bar')

const baz = () => console.log(‘baz’)

const foo = () => { console.log(‘foo’) bar() baz() }

foo()

Ce code imprime

foo
bar
baz

comme prévu.

Lorsque ce code s'exécute, d'abordfoo()est appelé. À l'intérieurfoo()nous appelons d'abordbar(), alors nous appelonsbaz().

À ce stade, la pile d'appels ressemble à ceci:

Call stack first example

La boucle d'événements à chaque itération regarde s'il y a quelque chose dans la pile d'appels et l'exécute:

Execution order first example

jusqu'à ce que la pile d'appels soit vide.

Exécution de la fonction de mise en file d'attente

L'exemple ci-dessus semble normal, il n'y a rien de spécial à ce sujet: JavaScript trouve des choses à exécuter, les exécute dans l'ordre.

Voyons comment différer une fonction jusqu'à ce que la pile soit dégagée.

Le cas d'utilisation desetTimeout(() => {}), 0)consiste à appeler une fonction, mais à l'exécuter une fois que toutes les autres fonctions du code ont été exécutées.

Prenons cet exemple:

const bar = () => console.log('bar')

const baz = () => console.log(‘baz’)

const foo = () => { console.log(‘foo’) setTimeout(bar, 0) baz() }

foo()

Ce code imprime, peut-être de manière surprenante:

foo
baz
bar

Lorsque ce code s'exécute, le premier foo () est appelé. Dans foo (), nous appelons d'abord setTimeout, en passantbarcomme argument, et nous lui demandons de s'exécuter immédiatement aussi vite que possible, en passant 0 comme minuterie. Ensuite, nous appelons baz ().

À ce stade, la pile d'appels ressemble à ceci:

Call stack second example

Voici l'ordre d'exécution de toutes les fonctions de notre programme:

Execution order second example

Pourquoi cela arrive-t-il?

La file d'attente des messages

Lorsque setTimeout () est appelé, le Browser ou Node.js démarre leminuteur. Une fois la minuterie expirée, dans ce cas immédiatement lorsque nous mettons 0 comme timeout, la fonction de rappel est placée dans leFile d'attente de messages.

La file d'attente de messages est également l'endroit où les événements déclenchés par l'utilisateur, tels que les événements de clic ou de clavier, oualler chercherles réponses sont mises en file d'attente avant que votre code n'ait la possibilité d'y réagir. Ou aussiDOMévénements commeonLoad.

La boucle donne la priorité à la pile d'appels, et elle traite d'abord tout ce qu'elle trouve dans la pile d'appels, et une fois qu'il n'y a rien là-dedans, elle va chercher des éléments dans la file d'attente de messages.

Nous n'avons pas à attendre des fonctions commesetTimeout, fetch ou autres choses pour faire leur propre travail, car ils sont fournis par le navigateur, et ils vivent sur leurs propres threads. Par exemple, si vous définissez lesetTimeouttimeout à 2 secondes, vous n'avez pas à attendre 2 secondes - l'attente se produit ailleurs.

File d'attente des travaux ES6

ECMAScript 2015a introduit le concept de Job Queue, qui est utilisé par Promises (également introduit dans ES6 / ES2015). C'est un moyen d'exécuter le résultat d'une fonction asynchrone dès que possible, plutôt que d'être placé à la fin de la pile d'appels.

Les promesses qui se résolvent avant la fin de la fonction en cours seront exécutées juste après la fonction en cours.

Je trouve bien l'analogie d'un tour de montagnes russes dans un parc d'attractions: la file d'attente des messages vous place au fond de la file d'attente, derrière toutes les autres personnes, où vous devrez attendre votre tour, tandis que la file d'attente est le ticket fastpass qui vous permet de faire un autre tour juste après avoir terminé le précédent.

Exemple:

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

Cette imprime

foo
baz
should be right after baz, before bar
bar

C'est une grande différence entre Promises (et Async / await, qui est construit sur des promesses) et les anciennes fonctions asynchrones viasetTimeout()ou d'autres API de plate-forme.

Conclusion

Cet article vous a présenté les éléments de base de la boucle d'événements Node.js.

C'est une partie essentielle de tout programme écrit en Node.js, et j'espère que certains des concepts expliqués ici vous seront utiles à l'avenir.

Téléchargez mon gratuitManuel de Node.js


Plus de didacticiels sur les nœuds: