2.2EventEmitter & programmation événementielle
Node est, dans son essence, événementiel (rappel du niveau 0 : la boucle d'événements). Ce paradigme se retrouve dans une classe centrale : l'EventEmitter (du module natif events). La programmation événementielle repose sur une idée simple et puissante : au lieu que le code appelle directement d'autres fonctions, des objets émettent des événements (« il s'est passé quelque chose ») et d'autres parties du code écoutent ces événements et y réagissent. C'est un mécanisme de découplage : l'émetteur ne sait pas qui l'écoute, l'écouteur ne sait pas d'où vient l'événement — ils communiquent par le nom de l'événement. Ce modèle est omniprésent en Node : de nombreux objets natifs SONT des EventEmitters (un serveur HTTP émet un événement à chaque requête, un stream émet des événements de données, etc.).
Le fonctionnement de l'EventEmitter tient en deux méthodes principales. .on(nomEvenement, callback) : ENREGISTRER un écouteur — « quand l'événement X est émis, exécute cette fonction ». .emit(nomEvenement, ...données) : ÉMETTRE un événement — « déclenche X, avec ces données » (tous les écouteurs de X sont alors appelés avec ces données). On crée son propre émetteur en instanciant EventEmitter (ou en faisant hériter une classe de lui). D'autres méthodes utiles : .once() (écouter un événement UNE seule fois, puis se désabonner automatiquement), .off()/.removeListener() (arrêter d'écouter), .emit('error', ...) (l'événement error a un traitement spécial — un émetteur qui émet error sans écouteur fait planter le programme, d'où l'importance de toujours écouter les erreurs). Pourquoi comprendre l'EventEmitter est important : (1) c'est le modèle sous-jacent de nombreuses API Node — les serveurs HTTP, les streams, les sockets, beaucoup de bibliothèques exposent leurs comportements via des événements ; savoir écouter des événements (.on('data', ...), .on('request', ...), .on('connection', ...)) est constant en Node. (2) Il permet de créer une architecture découplée et extensible dans vos propres applications (un système où des composants réagissent à des événements sans dépendre directement les uns des autres). (3) Il incarne la philosophie événementielle de Node. Vous n'écrirez pas forcément beaucoup d'EventEmitters personnalisés au début, mais vous les RENCONTREREZ et les UTILISEREZ constamment (écouter les événements des objets Node) — d'où l'importance de comprendre le modèle : émettre/écouter, découpler par le nom des événements. C'est un concept clé qui relie la nature événementielle de Node à la façon concrète dont on interagit avec ses API.
Vocabulaire de la section
- Programmation événementielle
- Modèle où des objets émettent des événements et d'autres parties du code y réagissent en écoutant ; découplage par le nom de l'événement.
- EventEmitter
- Classe native (module events) au cœur du modèle événementiel de Node ; de nombreux objets natifs (serveur HTTP, streams…) en héritent.
- .on() / .emit()
- on(événement, callback) : enregistrer un écouteur. emit(événement, données) : déclencher l'événement (tous ses écouteurs sont appelés avec les données).
- .once() / .off()
- once() : écouter un événement une seule fois (désabonnement auto). off()/removeListener() : arrêter d'écouter un événement.
- Événement error
- Événement spécial : un émetteur qui émet 'error' sans écouteur fait planter le programme — toujours écouter les erreurs.
Pourquoi la programmation événementielle (EventEmitter) est-elle centrale en Node ?
En pratique — Utiliser la programmation événementielle
- Créez un EventEmitter (`const emitter = new EventEmitter()`), enregistrez un écouteur avec `.on('event', callback)` et émettez avec `.emit('event', données)`.
- Passez des données lors de l'émission et récupérez-les dans l'écouteur ; ajoutez plusieurs écouteurs sur le même événement.
- Testez `.once()` (écoute unique) et écoutez toujours l'événement `'error'` (sinon un plantage).
- Repérez que des objets Node natifs sont des EventEmitters : écoutez par exemple les événements d'un serveur HTTP ou d'un stream (`.on('data', ...)`).
Points clés à retenir
- La PROGRAMMATION ÉVÉNEMENTIELLE (au cœur de Node) : des objets ÉMETTENT des événements, d'autres les ÉCOUTENT et réagissent — découplage par le nom de l'événement.
- L'EVENTEMITTER (module events) est la classe centrale ; de nombreux objets natifs en héritent (serveur HTTP, streams, sockets).
- Deux méthodes clés : `.on(événement, callback)` (écouter) et `.emit(événement, données)` (déclencher). Aussi : `.once()` (une fois), `.off()` (arrêter).
- Toujours écouter l'événement `'error'` (émis sans écouteur = plantage). Comprendre l'EventEmitter = comprendre comment interagir avec les API Node (`.on('data'/'request'/'connection')`).
Questions fréquentes
Pourquoi la programmation événementielle est-elle si présente en Node, et où la rencontre-t-on ?
La programmation événementielle est au CŒUR de Node parce qu'elle découle directement de son architecture asynchrone non-bloquante, et vous la rencontrez partout — la comprendre est essentiel pour utiliser les API Node avec aisance. Pourquoi elle est centrale en Node : rappelez-vous le modèle de Node (niveau 0) — non-bloquant, avec une boucle d'événements. Dans ce modèle, les choses se passent de façon ASYNCHRONE : une requête arrive, des données réseau se présentent, un fichier finit d'être lu — ce sont des ÉVÉNEMENTS qui surviennent à des moments imprévisibles. La façon naturelle de gérer cela est de RÉAGIR à ces événements : « quand une requête arrive, fais ceci », « quand des données arrivent, traite-les », « quand la connexion se ferme, nettoie ». C'est exactement la programmation événementielle : émettre/écouter des événements. Elle épouse parfaitement la nature asynchrone de Node — d'où son omniprésence. Où vous la RENCONTREZ (constamment) : (1) Les serveurs HTTP : un serveur Node émet un événement 'request' à chaque requête entrante — on l'écoute pour la traiter. Le serveur est un EventEmitter. (2) Les streams (section 2-3) : un flux de données émet des événements 'data' (un morceau de données est disponible), 'end' (le flux est terminé), 'error' (un problème). On lit un gros fichier ou une réponse réseau en écoutant ces événements. (3) Les sockets et connexions réseau : événements de connexion, de données, de fermeture. (4) Le processus : process émet des événements ('exit', signaux système). (5) De nombreuses bibliothèques : beaucoup de paquets npm exposent leurs comportements via des événements (une connexion à une base de données émet des événements, un client émet des événements, etc.). (6) Socket.IO / WebSockets (section 4-3) : le temps réel est massivement événementiel (émettre/recevoir des messages). En clair, dès que vous manipulez des I/O, du réseau, des flux, du temps réel en Node, vous êtes face à des événements. Savoir écouter un événement (objet.on('nomEvenement', callback)) est un geste que vous répéterez sans cesse. Ce que cela implique pour vous : (1) Vous devez COMPRENDRE le modèle (émettre/écouter, EventEmitter, .on/.emit) pour utiliser les API Node — même si vous ne créez pas vous-même beaucoup d'émetteurs, vous CONSOMMEZ constamment des événements. (2) Vous devez savoir ÉCOUTER les bons événements sur les objets Node (les données, la fin, les erreurs d'un stream ; les requêtes d'un serveur ; les messages d'une socket). (3) Vous devez toujours gérer les événements 'error' (ne pas les écouter peut faire planter le programme). (4) Vous CRÉEREZ parfois vos propres EventEmitters pour architecturer votre application de façon découplée (des composants qui réagissent à des événements métier sans dépendre directement les uns des autres) — utile pour l'extensibilité. Une image : la programmation événementielle est comme un système de notifications. Au lieu d'appeler directement quelqu'un (couplage fort), on « publie » qu'un événement s'est produit, et quiconque s'y est abonné réagit (découplage). Node fonctionne ainsi à tous les niveaux, parce que son travail (réagir à des I/O asynchrones) EST fondamentalement une affaire de réaction à des événements. Comprendre ce paradigme, c'est comprendre comment Node « pense » — et donc savoir travailler avec lui naturellement. C'est pourquoi l'EventEmitter, bien que discret, est un concept clé : il relie la philosophie de Node (événementielle, asynchrone) à la pratique quotidienne (écouter .on('data'), .on('request')…). Une fois ce modèle intégré, les API Node (qui exposent tant de choses par événements) deviennent limpides.
Quand créer mon propre EventEmitter plutôt que d'utiliser d'autres mécanismes (callbacks, Promises) ?
C'est une question d'architecture pertinente : callbacks/Promises et EventEmitter servent des besoins DIFFÉRENTS, et savoir lequel utiliser quand est une compétence de conception. La distinction fondamentale porte sur le NOMBRE de résultats et la RELATION entre producteur et consommateur. Utilisez callbacks / Promises / async-await quand : (1) Vous attendez UN résultat unique d'une opération (lire un fichier → un contenu ; interroger une base → un résultat). Une Promise représente UNE valeur future ; elle se résout UNE fois. C'est parfait pour « fais cette opération asynchrone et donne-moi son résultat ». (2) La relation est DIRECTE : vous appelez une fonction, elle vous rend un résultat (via await/callback). Un couplage direct « je demande, tu réponds ». C'est le cas de la grande majorité des opérations asynchrones (I/O, requêtes) — d'où la prééminence d'async/await. Utilisez un EventEmitter quand : (1) Un événement peut se produire PLUSIEURS FOIS. Une Promise se résout une seule fois ; un EventEmitter émet un événement autant de fois que nécessaire. Exemple : un serveur reçoit MANY requêtes au fil du temps → événement 'request' émis à chaque fois (pas une Promise unique). Un stream émet 'data' pour CHAQUE morceau de données. Le flux continu et répété d'événements se prête à l'EventEmitter, pas à une Promise. (2) Il y a un DÉCOUPLAGE souhaité : l'émetteur ne sait pas (ni ne se soucie de) qui écoute, et il peut y avoir PLUSIEURS écouteurs. Un événement « publié » peut être écouté par 0, 1 ou plusieurs consommateurs indépendants. C'est utile pour une architecture extensible : un composant émet « utilisateur créé », et divers écouteurs indépendants réagissent (envoyer un email, journaliser, mettre à jour des stats) sans que l'émetteur les connaisse. Ajouter un nouveau comportement = ajouter un écouteur, sans toucher à l'émetteur. (3) Vous modélisez un OBJET qui a un cycle de vie avec plusieurs types d'événements (un objet qui émet 'connect', 'data', 'error', 'close' au fil de son existence). En résumé : (1) UN résultat, relation directe → Promise/async-await (le cas courant). (2) PLUSIEURS occurrences, découplage, plusieurs écouteurs possibles → EventEmitter. Exemples pour clarifier : (1) « Lis ce fichier » → UN résultat → await fs.promises.readFile() (Promise). (2) « Traite chaque morceau de ce gros fichier au fur et à mesure » → PLUSIEURS morceaux → stream avec événements 'data' (EventEmitter). (3) « Interroge la base » → UN résultat → await (Promise). (4) « Réagis à chaque requête entrante du serveur » → PLUSIEURS requêtes → événement 'request' (EventEmitter). (5) « Notifie plusieurs parties du système qu'un utilisateur s'est inscrit, chacune réagissant à sa façon » → découplage, plusieurs écouteurs → EventEmitter personnalisé. Quand créer VOTRE propre EventEmitter : c'est plus rare au début, mais utile quand vous voulez une architecture DÉCOUPLÉE dans votre application — un endroit émet un événement métier (« commande passée », « paiement reçu »), et plusieurs parties du code y réagissent indépendamment, sans couplage direct. Cela rend le système extensible (ajouter une réaction = ajouter un écouteur) et modulaire. Mais n'abusez pas : pour de la logique simple et directe, un appel de fonction ou une Promise est plus clair qu'un système d'événements (qui ajoute de l'indirection). L'EventEmitter brille pour le découplage et les occurrences multiples, pas pour tout. La bonne pratique : (1) par défaut, pour les opérations asynchrones à résultat unique → async/await (le cas dominant) ; (2) pour les flux d'événements répétés (streams, serveurs, sockets) → vous CONSOMMEZ des EventEmitters (les API Node vous les fournissent) ; (3) pour découpler des composants de votre application autour d'événements métier → créez vos EventEmitters, avec parcimonie et quand le découplage apporte une vraie valeur. Comprendre cette distinction (résultat unique vs occurrences multiples, couplage direct vs découplage) vous fait choisir le bon outil et concevoir des architectures propres. C'est de la conception logicielle appliquée à Node.