Sommaire
- Pourquoi c'est bien : l'ergonomie et la fiabilité
- Pourquoi c'est bien : les performances
- Oui mais le parallélisme ?
- Oui mais les inconvénients ?
- Et quoi d'autre ?
Node.js utilise le pattern / modèle de concurrence Reactor, ou plus simplement mais moins précisément celui de boucle d'événement.
D'un point de vue concret, ça se traduit par :
- L'exécution du programme principal dans un thread unique
- La (quasi) totalité des fonctions d'I/O qui sont non bloquantes
- Une boucle d'événements qui démultiplexe les I/O entrantes et appelle les callbacks ad-hoc
Et du point de vue du programmeur, ça se traduit de la façon suivante :
import { readFile } from "node:fs";
readFile("toto.txt", (err, data) => {
console.log(data);
});
Ce qui n'est pas très pratique : à moins d'utiliser une abstraction idoine, le séquencement du programme est difficle à faire. Par exemple, si on veut lire deux fichiers de façon séquentielle, on écrirait :
readFile("toto.txt", (err, toto) => {
readFile("titi.txt", (err, titi) => {
console.log(toto, titi);
});
});
Ce qui, vous le conviendrez, est déjà très moche avec seulement deux opérations.
Ce problème est amélioré grâce à un objet qui s'appelle, une promesse, et qui comme son nom l'indique, contient la promesse d'une valeur, et qui permet d'indiquer les opérations ultérieures à faire sur la future valeur, au même titre qu'un tableau muni de la fonction flatMap permet de spécifier des operations à faire sur une valeur du tableau (on appelle ça une monade).
Avec un exemple de code, c'est beaucoup plus simple :
import { readFile } from "node:fs/promises";
const totoPromise = readFile("toto.txt");
const titiPromise = totoPromise.then((toto) => readFile("titi.txt"));
Promise.all([totoPromise, titiPromise]).then(([toto, titi]) =>
console.log(toto, titi),
);
On n'a plus le problème des callbacks l'un dans l'autre (appelé callback hell), mais ça reste assez verbeux, et pour exprimer que l'on souhaite travailler sur la promesse de toto et titi, on doit utiliser la fonction Promise.all.
Pour améliorer ça, on utilise un sucre syntaxique qui s'appelle async/await : le mot clé await peut être utilisé pour "extraire" la valeur d'une promesse. Ce mot clé ne peut être utilisé que dans une fonction déclarée async, et qui retourne toujours une promesse, ou à la racine du programme.
Ainsi, notre programme s'écrit de cette façon :
const toto = await readFile("toto.txt");
const titi = await readFile("titi.txt");
console.log(toto, titi);
Ce programme ressemble à un programme écrit dans un langage avec de l'I/O bloquante, mais ce n'est que du sucre syntaxique : l'exécution est en pratique identique à notre premier exemple avec des callbacks, ce qui signifie notamment que notre programme est toujours contrôlé par la boucle d'événements, et que notre thread unique peut exécuter d'autres ligne de code en attendant que le noyau nous informe que le contenu du fichier toto.txt ou titi.txt est arrivé.
Pourquoi c'est bien : l'ergonomie et la fiabilité
Niveau ergonomie, on a déjà vu que pour des cas simples, avec async/await, ce n'est presque pas plus compliqué que de faire de l'I/O bloquante.
Là où le gain est beaucoup plus important est que, vu que l'on n'a qu'un thread, il n'y a pas besoin d'utiliser de synchronisation entre threads. Par exemple créons un serveur TCP qui incrémente un compteur à chaque fois qu'un paquet est reçu :
import { createServer } from "node:net";
let i = 0;
createServer((socket) => {
socket.on("data", (data) => {
i++;
});
}).listen(4242);
Ce code est parfaitement sûr, alors que dans un langage où on traiterait les requêtes dans un pool de threads il faudrait verrouiller les accès à la variable i, ou utiliser des opérations atomiques (d'ailleurs, en C et en C++, incrémenter une variable n'est pas atomique).
L'autre avantage, en terme d'ergonomie, est qu'il est très facile de gérer la concurrence de façon fine. Par exemple, si l'on souhaite lire les fichiers toto et titi de façon concurrente on peut faire :
const totoPromise = readFile("toto.txt"); // L'exécution commence dès qu'on appelle readFile. readFile est non-bloquant, donc la ligne ci-dessous est exécutée immédiatement
const titiPromise = readFile("titi.txt");
console.log(await toto, await titi);
Ou, de façon plus idiomatique :
const [toto, titi] = await Promise.all([
readFile("toto.txt"),
readFile("titi.txt"),
]);
console.log(await toto, await titi);
Ou de façon encore plus concise :
const fnames = ["toto.txt", "titi.txt"];
const files = await Promise.all(fnames.map((fn) => readFile(fn)));
console.log(...files);
Pourquoi c'est bien : les performances
Il y a plusieurs raisons pourquoi, pour du code faisant beaucoup d'I/O, Node.js est très performant.
Le premier point, qu'on a déjà vu, est qu'il n'y a pas besoin de synchronisation. Dans un cas où il y a beaucoup de contention, et plus la machine a de coeurs et une architecture mémoire non uniforme, plus la synchronisation peut être très lente. Typiquement l'acquisition d'un mutex peut prendre, par exemple en Go, plusieurs centaines de nano-secondes, soit le temps qu'un thread unique aurait utilisé pour exécuter plusieurs centaines d'instructions.
Le 2e point, est que la concurrence n'a quasiment aucun surcoût mémoire. Par exemple, lorsqu'une socket tcp est ouverte, les coûts mémoire sont la socket dans le noyau, ainsi que les éventuels callbacks qui y sont attachés. Un langage qui utilise un green thread par socket, voire pire une thread par socket (ce qui d'ailleurs ne se fait pas vu le coût) aurait aussi le coût du thread, en particulier celui de la pile. Cela rend Node.js particulièrement adapté à la gestion de connexions persistantes, par exemple de websockets.
Oui mais le parallélisme ?
Un thread, c'est bien, mais comment faire davantage de traitements simultanés ou des calculs lourds sans bloquer le thread principal ?
Il y a principalement deux options :
- le scaling horizontal, typiquement pour des serveurs web : on lance autant de serveur qu'on a de processeurs. Cela peut se faire à l'aide de fonctions intégrées à node (typiquement le module cluster), d'outils liés à l'écosystème node (pm2), ou d'outils génériques (k8s, etc.)
- exécuter le travail dans un autre thread, et le récupérer de façon asynchrone. C'est le cas de certaines fonctions de la librairie standard (module crypto), peut se faire en C++ (sur le même mécanisme que crypto) ou en javascript (worker_threads)
Oui mais les inconvénients ?
Le principal inconvénient est que tout traitement lent bloque complètement le thread, ce qui rend difficile de maitriser la latence en queue de distribution. Ça peut par exemple être le cas d'un serveur web qui reçoit régulièrement des grosses requêtes longues à désérialiser. Sur d'autres modèles d'exécution le traitement lent serait préempté, ce qui nuirait moins à la latence.
Et quoi d'autre ?
J'ai essayer de rester bref, mais il y a plein d'autre intéressantes sur la concurrence en général (gestion de la concurrence, des files d'attente, etc.), et d'autres thèmes qui se prêtent bien au traitement asynchrone (streams, etc.)
Un autre point est que, bien que JavaScript se prête a priori assez mal à l'optimisation, l'optimiseur de V8 (le runtime de Node.js) est très performant, et du code qui n'utilise pas d'antipatterns de performance peut s'exécuter à des vitesses quasi-natives.
# What color is your function ?
Posté par chimrod (site web personnel) . Évalué à 9 (+8/-1).
https://journal.stuffwithstuff.com/2015/02/01/what-color-is-your-function/
Cet article a déjà dix ans, et les choses n’ont pas bougées depuis, les critiques levées sont toujours valides dès que je vois un await/async dans du code !
[^] # Re: What color is your function ?
Posté par fork_bomb . Évalué à 2 (+2/-1).
L'article est intéressant, mais je trouve que la gravité du "problème" évoqué n'est absolument pas en rapport avec la popularité de l'article.
Tout d'abord, la solution que préfère l'auteur (dernier paragraphe) sont les green threads. Ce n'est pas une meilleure solution mais un compromis différent, comme dit dans mon journal (besoin de gérer la synchronisation à la main, overhead sur chaque thread, profil de performance différent). Certes il n'y a plus le sujet du function coloring au niveau du langage, mais encore faut-il que les autres compromis conviennent.
Sur le fond, la raison principale pour laquelle le function coloring n'est pas un vrai problème, c'est que dans un programme bien architecturé, l'I/O est en haut de l'arbre des appels, et qu'au fur et à mesure qu'on rentre dedans on essaie d'avoir des fonctions pures, ou plus généralement qui peuvent facilement être testées.
D'ailleurs le function coloring n'est pas spécifique à async/await : une fonction qui prend en premier argument une connexion à la base de données, ou encore une référence à la requête en cours ne peut pas être appelée par une fonction qui n'a pas cette référence. Certes dans ce cas il y a des façons simples d'éviter le problème comme utiliser un singleton, du thread-local storage ou équivalent, mais ce n'est pas forcément une solution propre ou souhaitable.
Pour moi le seul cas où il y a un vrai problème est si l'on doit appeler une fonction async à partir d'une librairie qui ne le prévoit pas, par exemple un validateur XML qui permet de fournir un callback pour vérifier certains éléments, et on souhaite dans ce callback appeler un service web. Mais, en pratique, du moins dans l'écosystème node.js, c'est extrêmement marginal (personnellement je n'ai jamais vu le problème), et, vu que la grande majorité des librairies sont opensource il est facile de les modifier pour avoir le comportement qu'on souhaite.
Et, enfin, ce n'est pas vrai qu'il n'est pas possible d'appeler des fonctions async à partir d'une fonction sync. Ce n'est pas une bonne idée vu que ça bloque l'event loop, mais, par exemple en Node.js ça peut être fait en exécutant la fonction dans un worker thread et en attendant son résultat avec Atomics.wait.
[^] # Re: What color is your function ?
Posté par 🚲 Tanguy Ortolo (site web personnel) . Évalué à 5 (+2/-0).
Alors, par rapport aux goroutines que l'auteur semble préférer : https://vorpus.org/blog/notes-on-structured-concurrency-or-go-statement-considered-harmful/
[^] # Re: What color is your function ?
Posté par Julien Jorge (site web personnel) . Évalué à 3 (+1/-0).
L'auteur semble mélanger callbacks et concurrence. Ça n'a pas grand chose en commun. Si je comprends bien sa proposition permet d'avoir un point de synchro implicite en sortie de scope, ce qui semble pertinent mais je doute que ce soit toujours applicable.
# Syndrome de Stockholm?
Posté par jch . Évalué à 1 (+5/-5).
https://fr.wikipedia.org/wiki/Syndrome_de_Stockholm
[^] # Re: Syndrome de Stockholm?
Posté par fork_bomb . Évalué à 5 (+4/-0).
Mais encore ?
[^] # Re: Syndrome de Stockholm?
Posté par Yth (Mastodon) . Évalué à 10 (+15/-2).
Mon opinion perso sur Node, qui se traduit par la même conclusion que l'auteur au dessus :
Le JavaScript est un langage dramatique, atroce à coder, à débugger (mes expériences ont 10-15 ans, et déjà Firefox sauvait la vie, mais pas Chrome), et globalement c'était une souffrance encore plus grande que de coder en PHP 4.
Mais indispensable pour rendre une page web dynamique, le potentiel utile et fun est énorme.
Alors quand on a souffert des années à apprendre le bidule, à gérer les incompatibilités, les comportements aberrants, les mauvais design, etc. On a du mal à lâcher l'affaire, parce que le résultat est là, on fait des trucs cool sur son navigateur, et il n'y a pas vraiment d'alternative !
Alors un jour, des gens se sont dis « eh, si on capitalisait sur ce temps passé à souffrir face au JS, pour en mettre côté serveur aussi ? »
Et il y a du rationnel : utiliser la même bibliothèque côté client (navigateur) et serveur pour par exemple faire des websocket, c'est un peu la garantie que ça va rouler facilement, et surtout un seul point de débuggage, et pas deux trucs à apprendre dans deux langages différents.
Paf, on vient de déplacer toute la souffrance du JavaScript côté navigateur dans le JavaScript côté serveur.
On va souffrir d'un langage pas conçu pour ça, de ses défauts de conception, de son horreur à débugger, et de son écosystème complètement en vrac.
Et on va empirer le tout grâce à NPM, pour partager sa souffrance avec le monde entier, et faire un module « isEven » rationaliser
n%2==1.On va être content d'avoir un langage pour tout faire.
D'avoir souffert pour le maîtriser à peu près.
Et de réussir à faire du concret derrière.
On en vient à aimer la souffrance, en un sens à aimer le tortionnaire qui est le Javascript.
D'où le syndrome de Stockholm.
PS-Troll : C'est pareil pour les gens qui continuent à utiliser Windows, convaincus qu'il vont devoir souffrir à nouveau s'ils changent, ils s'enferment et en viennent à apprécier la souffrance connue, rassurante, habituelle.
[^] # Re: Syndrome de Stockholm?
Posté par fork_bomb . Évalué à -2 (+2/-5).
Franchement c'est un peu lamentable de passer du temps à écrire ce message qui n'a aucun détail précis (qui permettraient au lecteur de comprendre ce qui ne te plait pas dans le langage) plutôt qu'à mettre à jour tes connaissances.
Je pourrais répondre point par point mais je ne vois pas pourquoi je prendrais du temps alors que tu ne l'as pas fait.
Juste pour prendre ton premier point un peu précis :
Pour débugger un programme JavaScript, que ce soit Node.JS ou dans le navigateur, je met un point d'arrêt en cliquant dans mon éditeur ou dans les outils de développement, et quand je relance le programme, il s'y arrête.
[^] # Re: Syndrome de Stockholm?
Posté par Yth (Mastodon) . Évalué à 2 (+1/-1).
J'ai bien relu mon message.
Et il est très clair, dans ses prémisses, dans ce qu'il décrit, et dans son objectif.
Pour rappel, il s'agissait d'expliciter le premier message du fil de discussion qui citait sobrement le syndrome de Stockholm.
Je rappelle aussi que mes expériences datent pas mal, avec une comparaison à PHP 4.
4.
Je répète : 4 !
On a ici une sorte de contexte historique, marqué par des considérations sur l'origine de Node.js, et une partie de son évolution, avec NPM, et ses nombreuses dérives.
A aucun moment je ne parle de l'écosystème d'aujourd'hui.
Et je précise encore à la fin qu'il s'agit d'une opinion, très personnelle et ancienne, et pas d'un avis construit sur le langage tel qu'il existe aujourd'hui.
Encore une fois, en réponse au premier message qui parlait du syndrome de Stockholm.
Alors aujourd'hui, je t'en prie, écris, toi, un journal, pour expliquer comment on code en JS/Node, ce qu'il y a de positif, de bon, de cool, là-dedans. Aujourd'hui, pas historiquement.
[^] # Re: Syndrome de Stockholm?
Posté par fork_bomb . Évalué à 7 (+7/-1).
Par exemple le journal sous lequel tu commentes ?
[^] # Re: Syndrome de Stockholm?
Posté par Yth (Mastodon) . Évalué à 1 (+1/-2).
Si il parlait de « comment on code en JS/Node, ce qu'il y a de positif, de bon, de cool, là-dedans. » on pourrait dire oui.
Mais il parle de comment le parallélisme est géré dedans, et que cette façon de faire te semble cool.
[^] # Re: Syndrome de Stockholm?
Posté par Claude SIMON (site web personnel) . Évalué à 3 (+1/-0).
Concernant Node.js proprement dit, son auteur lui-même en a critiqué certains aspects, ce qui l'a amené à en co-créer une alternative : Deno…
Zelbinium: pour la génération qui crée, pas celle qui scrolle…
[^] # Re: Syndrome de Stockholm?
Posté par Pol' uX (site web personnel) . Évalué à 6 (+4/-0).
Un peu de retenue en ce jour d'anniversaire. :)
Adhérer à l'April, ça vous tente ?
[^] # Re: Syndrome de Stockholm?
Posté par porki . Évalué à 3 (+2/-0).
J’ai la même analyse que toi sur Javascript et je m’intéresse à wasm qui semble possible de faire le chemin inverse : un langage backend utilisable en frontend.
[^] # Re: Syndrome de Stockholm?
Posté par Colin Pitrat (site web personnel) . Évalué à 10 (+8/-0).
Effectivement c'est vraiment un langage de merde si les nombres pairs ont un modulo 2 égal à 1!
# libuv
Posté par Mathieu Schroeter (site web personnel, Mastodon) . Évalué à 6 (+5/-0).
A noter que c'est la libuv qui se charge de tout se travail d'I/O asynchrone. Cette bilbiothèque étant utilisées par de nombreux autres projets en dehors de nodejs.
https://github.com/libuv/libuv
[^] # Re: libuv
Posté par Tangi Colin . Évalué à 3 (+2/-0).
J'allais faire cette même réponse. La programmation asynchrone n'ai pas neuve, nodejs la en partie remis au gout du jour mais cella existe depuis que la programmation existe.
Pour des besoins simples, je dirais meme que libuv est overkill, gère trop de possibilités et de techno/paradigme différent et qu'une simple "event loop" via un epoll avec une machine d'état derrière fonctionne dans la plus part des cas simples où la robustesse est préférable à la performance pure niveau I/O.
Aujourd'hui si on cherche les performances pures et qu'on a pas de contrainte de multi-platforme, je partirai plutôt directement sur IOuring https://github.com/axboe/liburing.
Y a d'ailleurs de temps en temps des discussions qui arrivent sur la "mailing list" kernel pour évoquer les fait de passer l'ensemble des syscalls via IOuring (double ring buffer entre userspace et kernelspace, un pour les demandes, l'autre pour les réponses).
# no future
Posté par devnewton 🍺 (site web personnel) . Évalué à 3 (+0/-0). Dernière modification le 08 juin 2026 à 09:54.
Pourquoi async/await a été créé au niveau du langage ? Dans un autre langage pas de script, on a plutôt un type
Future<T>.Ce post est offensant ? Prévenez moi sur https://linuxfr.org/board
[^] # Re: no future
Posté par ff9097 . Évalué à 5 (+3/-0). Dernière modification le 08 juin 2026 à 12:37.
Il y a les deux. Une fonction async retourne
Future<T>[^] # Re: no future
Posté par thoasm . Évalué à 4 (+1/-0).
Avant il y avait des chaînes de
(fonction_qui_retourne_une_promise_t).then(fonction.then() …).then() …
Et les gens ont jugés que c'était pas du tout idéal voire l'enfer. Les mots clés permettent d'écrire des fonctions comme en procédural en plus lisible et d'applatir le truc sans profusion de callback explicite ou de fonction anonyme.
[^] # Re: no future
Posté par fork_bomb . Évalué à 4 (+3/-0). Dernière modification le 08 juin 2026 à 13:15.
Si je comprends bien comment Future (en Java) marche, il permet d'exécuter une fonction dans un autre thread (ou plus généralement d'une façon fournie par l'exécuteur), et ensuite d'obtenir son résultat avec la méthode Future.get(), qui est bloquante.
Avec le modèle Reactor, ce n'est pas possible (ni souhaité) d'avoir une fonction bloquante (vu que ça bloquerait tous les traitements du thread unique, et, dans le cas de Future.get provoquerait un deadlock), donc il n'est pas possible d'avoir une fonction Future.get, et donc d'extraire le résultat du calcul du Future.
On serait donc obligé d'utiliser le résultat de la Future sans l'extraire, par exemple avec
CompletableFuture.thenApply, ce qui, si on utilise ça partout, serait particulièrement illisible, d'où l'intérêt d'avoir un sucre syntaxique comme async/await pour gérer ça plus simplementEnvoyer un commentaire
Suivre le flux des commentaires
Note : les commentaires appartiennent à celles et ceux qui les ont postés. Nous n’en sommes pas responsables.