Quand vous souhaitez devenir meilleur en Node.js il est impératif de comprendre comment l’event-loop (fourni par le projet libuv) fonctionne et réagit pour notamment:
- Mieux comprendre la logique et l'ordre d'exécution de votre code.
- Optimiser et garantir les performances de votre application.
- Tout simplement approfondir vos connaissances sur votre stack technique.
Petit challenge, pouvez-vous deviner l’ordre des logs ?
async function a(val) {
console.log("A", val);
}
setImmediate(() => console.log("B"));
new Promise((res) => {
for (let id = 0; id < 1e9; id++) {}
setImmediate(() => console.log("C"));
process.nextTick(() => res("D"));
console.log("E");
}).then(console.log);
queueMicrotask(() => console.log("F"));
(async(res) => {
for (let id = 0; nid < 1e6; id++) {}
process.nextTick(() => console.log("G"));
return "H";
})().then(console.log);
process.nextTick(() => console.log("I"));
const promises = [];
let n = 0;
for (; n < 10; n++) promises.push(a(n));
console.lgo("J");
Promise.all(promises);
Quand il est question d’events-loop on parle souvent de Reactor pattern depuis 1996 (c'est le principe qui définit les fondamentaux et qui pourra notamment vous permettre de comprendre d’autres pattern de concurrence comme Proactor).
Dans le cadre d’une Event-loop/Reactor on parlera souvent aussi d'algorithme Round-robin et Demultiplexing d'évènements.
Schéma simple d’un Reactor (events loop).
Le réacteur prend en input un évènement (lire un fichier, envoyer un paquet sur le réseau) qui aura un cycle de vie prédéfini au sein de la loop en fonction de sa nature (et de l’implémentation). Les I/O bloquant seront, la plupart du temps, gérés au sein d’abstractions bas niveau fournies par le système comme epoll, kqueue et event ports (tout dépend du système d’exploitation cible). Quand il n’est pas possible d’utiliser les ressources du système, des threads seront bien souvent créés.
Une fois le traitement terminé le réacteur s’occupera de déclencher le callback lié à l’évènement pour signaler que le traitement est terminé (avec succès ou en erreur). Je parle ici de callback pour rester bas niveau, mais il peut s’agir d’une Promise/Future ou de toute autre structure ayant pour objectif de gérer la résolution d’un événement Asynchrone.
Lien bonus pour les motivés: EN Reactor - An Object Behavioral Pattern for Demultiplexing and Dispatching Handles for Synchronous Events
La notion d'event, event-driven et event-loop ne date pas d’hier et les premières apparitions date des années 80 (même si le pattern est devenu fort populaire depuis une dizaine d’années grâce à l’apparition de lib comme Libuv ou plus récemment Tokio sur Rust).
Il existe très probablement des librairies équivalentes ou des implémentations très sérieuses sur les différents runtime (Python, Ruby, PHP, Lua, Perl etc). Le langage de programmation Julia est d’ailleurs basé sur Libuv.
Aujourd’hui il devient très clair que le pattern a fait ses preuves et qu’il est très largement apprécié par les développeurs du monde entier pour construire des programmes concurrents (même s’il faut toujours garder en tête qu’il y aura toujours des points forts ainsi que des points faibles).
Il n’est donc ici pas uniquement question de devenir meilleur en Node.js ou en JavaScript, mais d’acquérir des compétences et des notions qui vous seront utiles tout au long de votre carrière.
Libuv est donc la librairie qui est utilisée dans Node.js pour l’event-loop. Son fonctionnement ne vous impacte pas directement dans votre code (elle est transparente pour les développeurs… c’est l’objectif de Node.js ^^).
Il est important de comprendre comment elle fonctionne a minima car l’exécution des différentes phases va définir comment votre code fonctionnera et dans quel ordre il sera exécuté (ce qui vous permettra de résoudre le challenge de l’introduction).
Le schéma ci-dessous est un schéma que j’ai construit pour représenter les différentes phases de l’event-loop (vous noterez la claire séparation entre votre code, la loop et le système d’exploitation).
Sur le sujet je vous recommande d’aller lire en premier lieu les pages suivantes :
- EN [Débutant] The Node.js Event Loop, Timers, and process.nextTick()
- EN [Débutant] Libuv design overview (documentation officielle de Libuv).
- EN [Débutant] An introduction to libuv.
Node.js event-loop par moi (version HD ici).
Divers articles de vulgarisation. Ils peuvent vous permettre de mieux comprendre divers sujets vus plus haut d’une façon plus abordable :
- [Débutant] What you should know to really understand the Node.js Event Loop
- [Débutant] Event Loop and the Big Picture — NodeJS Event Loop Part 1
- [Débutant] Timers, Immediates and Process.nextTick— NodeJS Event Loop Part 2
- [Débutant] Promises, Next-Ticks, and Immediates— NodeJS Event Loop Part 3
- [Débutant] JavaScript Visualized: Event Loop (pas forcément en lien direct avec Node.js)
- FR [Débutant] Démystifions la boucle d’événement (event loop) de Node.js
- [Intermédiaire] Introduction to Event Loop Utilization in Node.js
Divers talks sur Node.js et libuv (les deux derniers sont en français) :
- EN [Débutant] Everything You Need to Know About Node.js Event Loop
- EN [Débutant] Introduction to libuv: What's a Unicorn Velociraptor?
- EN [Débutant] The Node.js Event Loop: Not So Single Threaded
- EN [A savoir] Node.js Event-Loop: How even quick Node.js async functions can block the Event-Loop, starve I/O
- EN [Intermédiaire] Uncovering Libuv secrets, a practical approach - Santiago Gimeno
- [Intermédiaire] LXJS 2012 - Bert Belder - libuv
- FR [Débutant] Weektalk#1 ES-Community sur l’event-loop (date un petit peu mais toujours cool).
- FR [Débutant] Apprendre Node.js #5 - L'event loop (Vulgarisation de Louistiti).
⬅️ 🐢 Node.js: 📰 Conférences et Articles | ➡️ 🐢 Node.js: 👽 Native API (création d’addon natif en C, C++ et Rust)