hace 10 años
Los Event Emitters son un pilar fundamental en la construcción de aplicaciones eficientes y reactivas con Node.js. Representan la base para implementar el patrón publicador-suscriptor, permitiendo que diferentes componentes de tu aplicación se comuniquen y reaccionen a eventos de manera desacoplada. Imagina un centro de notificaciones centralizado donde:
- Se publican eventos: Señales con nombre que indican que algo ha ocurrido dentro de la aplicación.
- Se gestionan suscriptores: Componentes interesados en eventos específicos se registran como oyentes (listeners).
- Se disparan respuestas de oyentes: Cuando se emite un evento, todos los oyentes suscritos son notificados y ejecutan sus funciones de callback registradas.
Este mecanismo promueve una comunicación flexible y sin dependencias directas, asegurando que cada componente reaccione únicamente a los eventos que le conciernen, sin necesidad de conocer los detalles internos de otras partes del sistema. A pesar de su importancia, los Event Emitters a veces se perciben como complejos de entender y aplicar. En este artículo, vamos a solidificar tu conocimiento explorando diez casos de uso relevantes y prácticos.

- 1. Aplicación de Chat en Tiempo Real
- 2. Barra de Progreso Simple
- 3. Arquitectura de Plugins
- 4. Sistema de Gestión de Estado
- 5. Colas de Tareas Asíncronas
- 6. Simulación de Eventos de Red
- 7. Observables Personalizados
- 8. Framework de Interfaz de Usuario (UI)
- 9. Motor de Workflow Simple
- 10. Barra de Progreso de Carga de Archivos
- Preguntas Frecuentes sobre Event Emitters
1. Aplicación de Chat en Tiempo Real
Los Event Emitters son ideales para crear aplicaciones en tiempo real donde los usuarios reciben actualizaciones instantáneas sin necesidad de recargar la página. En este escenario, cada usuario se conecta a un servidor central que actúa como un emisor de eventos. Cuando un usuario envía un mensaje, el servidor emite un evento con la información del mensaje. Todos los usuarios conectados, que están suscritos a este evento, reciben el mensaje de forma inmediata.
const events = require('events'); const http = require('http'); const io = require('socket.io')(http); const server = http.createServer(); const messageEmitter = new events.EventEmitter(); io.on('connection', (socket) => { // Usuario conectado socket.emit('connected'); // Usuario envia un mensaje socket.on('message', (message) => { messageEmitter.emit('newMessage', message); }); // Emitir nuevos mensajes recibidos por el usuario messageEmitter.on('newMessage', (message) => { socket.emit('message', message); }); }); server.listen(3000, () => { console.log('Servidor escuchando en el puerto 3000'); }); En este ejemplo, messageEmitter actúa como el centro de eventos para los mensajes. Cuando un usuario envía un mensaje, se emite el evento newMessage con los datos del mensaje. Todos los usuarios suscritos a newMessage reciben la actualización del mensaje al instante, permitiendo una comunicación en tiempo real sin recargas de página.
2. Barra de Progreso Simple
Los Event Emitters pueden ser utilizados para actualizar una barra de progreso de manera asíncrona. Una tarea en ejecución emite eventos que indican diferentes etapas del progreso (por ejemplo, "iniciado", "progreso", "completado"). Un componente separado se suscribe a estos eventos y actualiza la barra de progreso en consecuencia.
const events = require('events'); class Task extends events.EventEmitter { constructor() { super(); } start() { this.emit('started'); // Simular algo de trabajo setTimeout(() => { this.emit('progress', 50); setTimeout(() => { this.emit('progress', 100); this.emit('completed'); }, 1000); }, 2000); } } const task = new Task(); task.on('started', () => { console.log('Tarea iniciada'); }); task.on('progress', (progress) => { console.log(`Progreso: ${progress}%`); }); task.on('completed', () => { console.log('Tarea completada'); }); task.start(); La clase Task emite eventos en diferentes etapas de su ejecución. Un componente separado (no mostrado aquí) se suscribiría a estos eventos y actualizaría la barra de progreso. Este desacoplamiento permite actualizaciones de la interfaz de usuario flexibles sin acoplar la tarea y la implementación de la barra de progreso.
3. Arquitectura de Plugins
Los Event Emitters facilitan la comunicación entre una aplicación central y sus plugins. La aplicación principal emite eventos para acciones específicas, y los plugins se suscriben a estos eventos para realizar acciones correspondientes. Esto permite extender la funcionalidad de la aplicación sin modificar el código central.
const events = require('events'); class Application extends events.EventEmitter { constructor() { super(); } triggerEvent(eventName, data) { this.emit(eventName, data); } } class Plugin1 extends events.EventEmitter { constructor(app) { super(); app.on('someEvent', this.handleEvent.bind(this)); } handleEvent(data) { console.log('Plugin 1 recibió el evento:', data); } } const app = new Application(); const plugin1 = new Plugin1(app); app.triggerEvent('someEvent', { message: '¡Hola desde la app!' }); La clase Application emite eventos para acciones específicas (como someEvent). La clase Plugin1 se suscribe a estos eventos y ejecuta acciones cuando se activan. Esto permite que los plugins extiendan la funcionalidad de la aplicación central sin modificarla directamente.
4. Sistema de Gestión de Estado
Los Event Emitters pueden impulsar sistemas ligeros de gestión de estado. Cuando el estado de la aplicación cambia, la lógica central emite un evento con el estado actualizado. Los componentes que escuchan este evento pueden actualizar sus vistas en consecuencia, asegurando la consistencia en toda la aplicación.
const events = require('events'); class Store extends events.EventEmitter { constructor(initialState) { super(); this.state = initialState; } getState() { return this.state; } setState(newState) { this.state = newState; this.emit('stateChange', this.state); } } const store = new Store({ count: 0 }); const component1 = { render() { console.log('Componente 1: Contador:', store.getState().count); }, }; const component2 = { render() { console.log('Componente 2: Contador:', store.getState().count); }, }; store.on('stateChange', () => { component1.render(); component2.render(); }); store.setState({ count: 1 }); // Dispara re-render en ambos componentes La clase Store actúa como el gestor central del estado y emisor de eventos. Cuando el estado cambia, se emite el evento stateChange con el estado actualizado. Los componentes que escuchan este evento actualizan sus vistas para reflejar el nuevo estado. Este patrón mantiene los componentes sincronizados con el estado de la aplicación sin propagación manual.
5. Colas de Tareas Asíncronas
Los Event Emitters se pueden usar para gestionar tareas asíncronas en una cola. Las tareas se añaden a la cola como eventos, y un componente worker (trabajador) escucha estos eventos y ejecuta las tareas secuencialmente. Esto promueve el orden y el procesamiento eficiente de operaciones asíncronas.
const events = require('events'); class TaskQueue extends events.EventEmitter { constructor() { super(); this.queue = []; } addTask(task) { this.queue.push(task); this.emit('taskAdded'); } startWorker() { this.on('taskAdded', () => { if (this.queue.length) { const task = this.queue.shift(); task(); } }); } } const queue = new TaskQueue(); const task1 = () => { console.log('Tarea 1'); }; const task2 = () => { console.log('Tarea 2'); }; queue.addTask(task1); queue.addTask(task2); queue.startWorker(); La clase TaskQueue almacena tareas en una cola y emite eventos taskAdded cuando se añaden nuevas tareas. La función startWorker escucha los eventos taskAdded y ejecuta la tarea más antigua de la cola. Esto asegura que las tareas se procesen en orden, incluso si se añaden de forma asíncrona.
6. Simulación de Eventos de Red
Los Event Emitters pueden simular eventos de red para propósitos de prueba. Emmitiendo eventos como connect, data y error, los desarrolladores pueden escribir casos de prueba que ejerciten diferentes escenarios de red sin depender de servicios externos.

const events = require('events'); class MockSocket extends events.EventEmitter { constructor() { super(); } connect() { this.emit('connect'); setTimeout(() => { this.emit('data', { message: '¡Hola desde el servidor!' }); }, 1000); } } const socket = new MockSocket(); socket.on('connect', () => { console.log('Socket conectado'); }); socket.on('data', (data) => { console.log('Datos recibidos:', data); }); socket.connect(); La clase MockSocket simula un socket de red y emite eventos como connect y data. Esto permite a los desarrolladores probar el comportamiento de su código en diferentes escenarios de red sin configurar conexiones reales.
7. Observables Personalizados
Los Event Emitters pueden usarse para crear observables personalizados. Un observable emite valores a lo largo del tiempo, y los suscriptores pueden registrarse para recibir estos valores. Los Event Emitters ofrecen un enfoque familiar para construir observables simples sin bibliotecas externas.
const events = require('events'); class Observable extends events.EventEmitter { constructor(subscribeFunction) { super(); this.subscribeFunction = subscribeFunction; } subscribe(onNext, onError, onComplete) { const subscription = this.subscribeFunction(onNext, onError, onComplete); return { unsubscribe: () => { subscription.unsubscribe(); }, }; } next(value) { this.emit('next', value); } error(error) { this.emit('error', error); } complete() { this.emit('complete'); } } const observable = new Observable((onNext, onError, onComplete) => { // Simular un flujo de datos asíncrono setTimeout(() => onNext('Valor 1'), 1000); setTimeout(() => onNext('Valor 2'), 2000); setTimeout(() => onComplete(), 3000); return { unsubscribe: () => console.log('Desuscrito'), }; }); const subscription = observable.subscribe( (value) => console.log('Recibido:', value), (error) => console.error('Error:', error), () => console.log('Completado') ); subscription.unsubscribe(); // Desuscribirse después de un tiempo La clase Observable envuelve una subscribeFunction subyacente que gestiona las suscripciones y la emisión de valores. Los suscriptores pueden llamar a subscribe para recibir valores o errores emitidos. Esto proporciona un marco simple para construir observables personalizados utilizando Event Emitters.
8. Framework de Interfaz de Usuario (UI)
Los Event Emitters pueden ser utilizados para crear frameworks de UI básicos. Cuando los componentes de la UI interactúan con el usuario o cambian de estado, emiten eventos. Otros componentes pueden suscribirse a estos eventos y actualizarse en consecuencia, resultando en interacciones de UI dinámicas y reactivas.
const events = require('events'); class Button extends events.EventEmitter { constructor(text) { super(); this.text = text; } click() { this.emit('click'); } } const button = new Button('Click me'); button.on('click', () => { console.log('¡Botón clickeado!'); }); button.click(); La clase Button emite un evento click cuando se hace clic en él. Otros componentes pueden escuchar este evento y realizar acciones, como actualizar otro elemento de la UI o disparar una funcionalidad específica. Este patrón promueve componentes de UI desacoplados que reaccionan a eventos en lugar de ser manipulados directamente.
9. Motor de Workflow Simple
Los Event Emitters pueden usarse para orquestar workflows donde las tareas dependen unas de otras. Cada tarea emite eventos cuando comienza, finaliza o encuentra errores. Otras tareas pueden escuchar estos eventos y disparar su propia ejecución en consecuencia, definiendo un workflow flexible basado en disparadores dirigidos por eventos.
const events = require('events'); class Task extends events.EventEmitter { constructor(name, taskFn) { super(); this.name = name; this.taskFn = taskFn; } run(previousTask) { if (previousTask) { previousTask.on('complete', () => { this.taskFn() .then(() => this.emit('complete')) .catch((error) => this.emit('error', error)); }); } else { this.taskFn() .then(() => this.emit('complete')) .catch((error) => this.emit('error', error)); } } } const task1 = new Task('Descargar Datos', () => new Promise((resolve, reject) => { // Simular descarga de datos setTimeout(() => resolve('Datos descargados'), 1000); })); const task2 = new Task('Procesar Datos', (data) => new Promise((resolve, reject) => { // Simular procesamiento de datos descargados setTimeout(() => resolve('Datos procesados'), 2000); })); task1.run(); 10. Barra de Progreso de Carga de Archivos
Los Event Emitters son útiles para rastrear y mostrar el progreso de la carga de archivos. El componente de carga emite eventos en diferentes etapas (por ejemplo, "carga iniciada", "actualización de progreso", "carga completada") con información relevante (por ejemplo, porcentaje cargado). Un componente de barra de progreso se suscribe a estos eventos y actualiza su visualización en consecuencia, proporcionando retroalimentación en tiempo real al usuario.
const events = require('events'); const fs = require('fs'); class Uploader extends events.EventEmitter { constructor(filePath, targetPath) { super(); this.filePath = filePath; this.targetPath = targetPath; this.fileSize = fs.statSync(filePath).size; this.uploaded = 0; } startUpload() { this.emit('uploadStarted'); const stream = fs.createReadStream(this.filePath); const writeStream = fs.createWriteStream(this.targetPath); stream.on('data', (chunk) => { this.uploaded += chunk.length; this.emit('progressUpdate', this.uploaded / this.fileSize); }); stream.on('end', () => { this.emit('uploadComplete'); }); stream.pipe(writeStream); } } const uploader = new Uploader('my-file.txt', 'uploads/my-file.txt'); const progressBar = { updateProgress(progress) { console.log(`Progreso de carga: ${Math.round(progress * 100)}%`); }, }; uploader.on('uploadStarted', () => console.log('Carga iniciada')); uploader.on('progressUpdate', (progress) => progressBar.updateProgress(progress)); uploader.on('uploadComplete', () => console.log('Carga completada')); uploader.startUpload(); Preguntas Frecuentes sobre Event Emitters
¿Qué es un Event Emitter en NodeJS?
Un Event Emitter es un objeto en NodeJS que facilita la implementación del patrón de diseño publicador-suscriptor. Permite que los objetos emitan eventos con nombre que otros objetos (suscriptores u oyentes) pueden escuchar y a los que pueden responder. Piensa en ello como un sistema de mensajería interna dentro de tu aplicación.
¿Cuándo deberías usar Event Emitters?
Los Event Emitters son especialmente útiles en situaciones donde necesitas desacoplar componentes, manejar operaciones asíncronas, o implementar patrones de comunicación uno-a-muchos. Son ideales para:
- Comunicación en tiempo real: Chats, notificaciones push.
- Gestión de progreso: Barras de progreso para tareas largas.
- Arquitecturas basadas en plugins: Extender funcionalidades sin modificar el núcleo.
- Gestión de estado: Centralizar y distribuir cambios de estado.
- Procesamiento asíncrono: Colas de tareas, flujos de datos.
- Simulación y pruebas: Simular eventos externos para pruebas unitarias.
- Interfaces de usuario reactivas: Componentes UI que reaccionan a eventos.
- Workflows y orquestación de tareas: Definir dependencias entre tareas asíncronas.
¿Cuál es un ejemplo sencillo de un Event Emitter?
Imagina un botón en una interfaz de usuario. Cuando haces clic en el botón, este emite un evento llamado "click". Otros componentes de la aplicación pueden suscribirse a este evento "click" y ejecutar código específico en respuesta a él. El botón en sí no necesita saber qué componentes están escuchando o qué harán; simplemente emite el evento. Este es un ejemplo básico de cómo un Event Emitter permite la comunicación desacoplada.
En resumen, los Event Emitters son una herramienta poderosa en Node.js para construir aplicaciones modulares, reactivas y fáciles de mantener. Al entender sus casos de uso y cómo implementarlos, podrás llevar tus habilidades de desarrollo en Node.js al siguiente nivel.
