EventEmitter est la classe du module events de Node qui permet à un objet d'annoncer des événements nommés. Vous enregistrez un listener avec emitter.on('name', fn) et vous le déclenchez avec emitter.emit('name', ...args) ; chaque listener de ce nom s'exécute, dans l'ordre où il a été ajouté, avec les arguments passés. Les serveurs HTTP, les streams et process sont tous des EventEmitters, donc les mêmes quelques méthodes marchent partout dans Node.
Les deux listeners s'exécutent entre before emit et after emit : emit les appelle de façon synchrone et ne rend la main qu'une fois qu'ils ont fini. Inversez les deux appels à shop.on et les deux lignes du milieu s'inversent aussi.
on, once et off
on garde un listener jusqu'à ce que vous le retiriez, once le retire après le premier appel, et off (identique à removeListener) en retire un. Pour retirer un listener, il faut la même fonction que celle ajoutée, donc donnez-lui un nom au lieu de passer une fonction fléchée directement.
first open ever s'affiche une seule fois, après Ada. Une fois logOpen retiré, plus personne n'écoute, donc le dernier emit renvoie false ; door.removeAllListeners('open') aurait supprimé les deux listeners en un seul appel.
| Méthode | Rôle |
|---|---|
on(name, fn), addListener | Appelle fn à chaque emit |
once(name, fn) | Appelle fn au prochain emit seulement |
prependListener(name, fn) | Comme on, mais passe avant les listeners existants |
off(name, fn), removeListener | Retire cette fonction |
emit(name, ...args) | Appelle les listeners, renvoie true s'il y en avait |
listenerCount(name) | Le nombre de listeners de l'événement |
L'événement error
'error' est un cas à part. Si vous l'émettez et que personne n'écoute, emit lève l'erreur, et dans un vrai programme une exception non interceptée fait planter le processus. Ajoutez toujours un listener 'error' aux emitters qui peuvent échouer, comme les streams et les serveurs.
Sans listener, emit lève l'erreur et le catch la récupère ; une fois le listener ajouté, le même emit se contente de l'appeler et le programme continue. Tout autre nom d'événement sans listener est ignoré sans bruit.
Étendre EventEmitter dans votre propre classe
La façon habituelle de l'utiliser est de faire hériter votre classe de EventEmitter, d'appeler super() dans le constructeur et this.emit quand quelque chose se produit. Les appelants s'abonnent ensuite à votre objet comme à n'importe quel objet Node.
Le programme affiche 25%, 50%, 75%, 100% puis photo.png uploaded. Placez up.start() au-dessus des deux lignes up.on et rien ne s'affiche : les événements sont émis avant que quiconque n'écoute.
Le même piège existe quand on émet depuis un constructeur. Les bibliothèques qui doivent émettre tout de suite enveloppent l'appel dans process.nextTick, qui s'exécute après les lignes on de l'appelant (voir la page sur l'event loop).
Attendre un événement avec events.once
events.once(emitter, name) renvoie une promesse qui se résout avec les arguments de l'événement, sous forme de tableau, la prochaine fois qu'il est émis. Un événement ponctuel devient ainsi quelque chose que vous pouvez await, et la promesse est rejetée si l'emitter émet 'error' d'abord.
L'await met le code en pause jusqu'à ce que le timer émette ready, puis le tableau est déstructuré en deux arguments. Pour un événement émis de nombreuses fois, events.on(emitter, name) donne un itérateur asynchrone que vous pouvez parcourir avec for await.
Erreurs fréquentes
Ajouter un listener dans une fonction appelée souvent. Appeler on dans chaque handler de requête empile les listeners et la mémoire. Ajoutez-le une seule fois, ou utilisez once.
Retirer une fonction écrite en ligne. off('data', () => ...) ne retire rien, car cette fonction fléchée est une nouvelle fonction. Gardez une référence à la fonction ajoutée.
S'attendre à ce que les listeners s'exécutent plus tard. emit est synchrone, donc un listener lent retarde le code qui a émis. Marquer le listener async n'aide pas, car son code s'exécute toujours sur le même thread jusqu'au premier await. Découpez le travail lourd en morceaux séparés par des setImmediate, ou déplacez-le dans un worker thread.
Questions fréquentes
EventEmitter est-il asynchrone ?
Non. emit appelle chaque listener immédiatement, l'un après l'autre, et rend la main quand le dernier a fini. Le code d'un listener peut lancer un travail asynchrone, mais le listener lui-même est appelé de façon synchrone.
Que signifie l'avertissement MaxListenersExceededWarning ?
Node prévient quand plus de 10 listeners sont ajoutés pour un même événement, car cela veut souvent dire qu'un listener est ajouté à chaque requête sans jamais être retiré. Retirez les listeners en trop, ou appelez emitter.setMaxListeners(n) si vous en avez vraiment besoin de plus.
Que renvoie emit ?
true si l'événement avait au moins un listener, false si personne n'écoutait. Il ne renvoie pas ce que renvoient les listeners.
Quels objets Node.js sont des EventEmitters ?
La plupart des objets du cœur de Node : les serveurs HTTP, les requêtes et les réponses, les streams, les sockets, process et les interfaces readline. C'est pour cela qu'ils partagent tous on, once et off.