EventEmitter es la clase del módulo events de Node que permite a un objeto anunciar eventos con nombre. Registras un listener con emitter.on('name', fn) y lo disparas con emitter.emit('name', ...args); se ejecuta cada listener de ese nombre, en el orden en que se añadió, con los argumentos que pasaste. Los servidores HTTP, los streams y process son todos EventEmitters, así que los mismos pocos métodos sirven en todo Node.
Los dos listeners se ejecutan entre before emit y after emit: emit los llama de forma síncrona y solo devuelve cuando terminan. Intercambia las dos llamadas a shop.on y las dos líneas del medio también se intercambian.
on, once y off
on mantiene un listener hasta que lo quitas, once lo quita después de la primera llamada, y off (lo mismo que removeListener) quita uno. Para quitar un listener necesitas la misma función que añadiste, así que ponle nombre en lugar de pasar una arrow function en línea.
first open ever se imprime una vez, después de Ada. Una vez quitado logOpen, nadie escucha, así que el último emit devuelve false; door.removeAllListeners('open') habría quitado los dos listeners de una sola llamada.
| Método | Qué hace |
|---|---|
on(name, fn), addListener | Llama a fn en cada emit |
once(name, fn) | Llama a fn solo en el siguiente emit |
prependListener(name, fn) | Como on, pero se ejecuta antes que los listeners existentes |
off(name, fn), removeListener | Quita esa función |
emit(name, ...args) | Llama a los listeners, devuelve true si había alguno |
listenerCount(name) | Cuántos listeners tiene el evento |
El evento error
'error' es especial. Si lo emites y nadie escucha, emit lanza el error, y en un programa real un throw sin capturar tumba el proceso. Añade siempre un listener de 'error' a los emitters que pueden fallar, como streams y servidores.
Sin listener, emit lanza el error y el catch lo recoge; con el listener añadido, el mismo emit simplemente lo llama y el programa sigue. Cualquier otro nombre de evento sin listeners se ignora sin más.
Extender EventEmitter en tu propia clase
Lo habitual es hacer que tu clase extienda EventEmitter, llamar a super() en el constructor y a this.emit cuando pasa algo. Así quien la usa se suscribe a tu objeto igual que a cualquier objeto de Node.
Imprime 25%, 50%, 75%, 100% y luego photo.png uploaded. Mueve up.start() por encima de las dos líneas up.on y no se imprime nada: los eventos se disparan antes de que nadie escuche.
La misma trampa aparece al emitir desde un constructor. Las librerías que necesitan emitir enseguida envuelven la llamada en process.nextTick, que se ejecuta después de las líneas on de quien llama (consulta la página del event loop).
Esperar un evento con events.once
events.once(emitter, name) devuelve una promesa que se resuelve con los argumentos del evento, como array, la próxima vez que se emita. Convierte un evento de una sola vez en algo que puedes esperar con await, y se rechaza si el emitter emite antes 'error'.
El await se detiene hasta que el timer emite ready, y luego el array se desestructura en los dos argumentos. Para un evento que se dispara muchas veces, events.on(emitter, name) te da un iterador asíncrono que puedes recorrer con for await.
Errores comunes
Añadir un listener dentro de una función que se ejecuta a menudo. Llamar a on en cada request handler acumula listeners y memoria. Añádelo una vez, o usa once.
Quitar una función en línea. off('data', () => ...) no quita nada, porque esa arrow function es una nueva. Guarda una referencia a la función que añadiste.
Esperar que los listeners se ejecuten más tarde. emit es síncrono, así que un listener lento retiene el código que emitió. Marcar el listener como async no ayuda, porque su código sigue ejecutándose en el mismo hilo hasta el primer await. Divide el trabajo pesado en partes con setImmediate entre ellas, o llévalo a un worker thread.
Preguntas frecuentes
¿EventEmitter es asíncrono?
No. emit llama a cada listener en el acto, uno detrás de otro, y devuelve cuando el último termina. El código dentro de un listener puede lanzar trabajo asíncrono, pero el listener en sí se llama de forma síncrona.
¿Qué significa el aviso MaxListenersExceededWarning?
Node avisa cuando se añaden más de 10 listeners a un mismo evento, porque eso suele indicar que se añade un listener en cada petición y nunca se quita. Quita los listeners de más, o llama a emitter.setMaxListeners(n) si de verdad necesitas más.
¿Qué devuelve emit?
true si el evento tenía al menos un listener, false si nadie escuchaba. No devuelve lo que devuelven los listeners.
¿Qué objetos de Node.js son EventEmitters?
La mayoría de los objetos del core de Node: servidores HTTP, requests y responses, streams, sockets, process y las interfaces de readline. Por eso todos comparten on, once y off.