Introducción
Contenido
En este tutorial, vamos a echar un vistazo al nativo de Node EventEmitter
clase. Aprenderá sobre eventos, lo que puede hacer con un EvenEmitter
y cómo aprovechar los eventos en su aplicación.
También cubriremos qué otros módulos nativos se extienden desde el EventEmitter
clase y algunos ejemplos para comprender lo que sucede detrás de escena.
Así que, en pocas palabras, cubriremos casi todo lo que necesita saber sobre EventEmitter
clase.
Usaremos algunas características básicas de ES6, como clases de JavaScript y funciones de flecha en este tutorial. Es útil, pero no obligatorio, si tiene algún conocimiento previo de la sintaxis de ES6.
¿Qué es un evento?
Todo un paradigma de software gira en torno a los eventos y su uso. La arquitectura impulsada por eventos es relativamente común hoy en día y las aplicaciones impulsadas por eventos producen, detectan y reaccionan a diferentes tipos de eventos.
Podría decirse que el núcleo de Node.js se basa en parte en eventos, ya que muchos módulos nativos, como el sistema de archivos (fs
), y stream
módulo están escritos como EventEmitter
s mismos.
En la programación dirigida por eventos, un evento es el resultado de una o varias acciones. Esto puede ser una acción del usuario o una salida periódica de un sensor, por ejemplo.
Puede ver los programas impulsados por eventos como modelos de publicación y suscripción en los que un editor desencadena eventos y los suscriptores los escuchan y actúan en consecuencia.
Por ejemplo, supongamos que tenemos un servidor de imágenes donde los usuarios pueden cargar imágenes. En la programación impulsada por eventos, una acción como cargar la imagen emitiría un evento. Para hacer uso de él, también habría 1..n
suscriptores a ese evento.
Una vez que se activa el evento de carga, un suscriptor puede reaccionar enviando un correo electrónico al administrador del sitio web, haciéndole saber que un usuario ha subido una foto. Otro suscriptor podría recopilar información sobre la acción y conservarla en la base de datos.
Estos eventos suelen ser independientes entre sí, aunque también pueden ser dependientes.
¿Qué es un EventEmitter?
los EventEmitter
class es una clase incorporada que reside en el events
módulo. Según la documentación:
Gran parte de la API principal de Node.js se basa en una arquitectura idiomática basada en eventos asíncrona en la que ciertos tipos de objetos (llamados «emisores») emiten eventos con nombre que causan Function
objetos («oyentes») que se llamarán »
Esta clase puede, hasta cierto punto, describirse como una implementación auxiliar del modelo pub / sub, ya que ayuda a los emisores de eventos (editores) a publicar eventos (mensajes) y a los oyentes (suscriptores) a actuar sobre estos eventos, de una manera simple.
Crear EventEmitters
Dicho esto, sigamos adelante y creemos un EventEmitter
. Esto se puede hacer creando una instancia de la propia clase o implementándola a través de una clase personalizada y luego creando una instancia de esa clase.
Crear un objeto EventEmitter
Comencemos con un objeto emisor de eventos simple. Crearemos un EventEmitter
que emitirá un evento que contiene información sobre el tiempo de actividad de la aplicación, cada segundo.
Primero, importe el EventEmitter
clase de la events
módulos:
const { EventEmitter } = require('events');
Entonces creemos un EventEmitter
:
const timerEventEmitter = new EventEmitter();
Publicar un evento desde este objeto es tan fácil como:
timerEventEmitter.emit("update");
Hemos especificado el nombre del evento y lo hemos publicado como evento. Sin embargo, no pasa nada ya que no hay un oyente que reaccione a este evento. Hagamos que este evento se repita cada segundo.
Utilizando el setInterval()
método, se crea un temporizador que publicará el update
evento cada segundo:
let currentTime = 0;
// This will trigger the update event each passing second
setInterval(() => {
currentTime++;
timerEventEmitter.emit('update', currentTime);
}, 1000);
los EventEmitter
instancia acepta un nombre de evento y un conjunto arbitrario de argumentos. En este caso, hemos pasado eventName
como update
y el currentTime
como el tiempo desde el inicio de la aplicación.
Activamos el emisor a través del emit()
método, que impulsa el evento con la información que hemos proporcionado.
Con nuestro emisor de eventos listo, suscribamos un detector de eventos:
Te puede interesar:Procesadores de lenguaje de programacióntimerEventEmitter.on('update', (time) => {
console.log('Message Received from publisher');
console.log(`${time} seconds passed since the program started`);
});
Utilizando el on()
, pasando el nombre del evento para especificar a cuál nos gustaría adjuntar un oyente, nos permite crear oyentes. Sobre el update
evento, se ejecuta un método que registra el tiempo. Puede agregar el mismo oyente una y otra vez, y cada uno se suscribirá al evento.
El segundo argumento de la on()
La función es una devolución de llamada que puede aceptar cualquier cantidad de datos adicionales emitidos por el evento. Cada oyente puede elegir qué datos quiere, una vez que se mantiene el orden.
Ejecutar este script debería producir:
Message Received from publisher
1 seconds passed since the program started
Message Received from publisher
2 seconds passed since the program started
Message Received from publisher
3 seconds passed since the program started
...
Por el contrario, podemos utilizar el once()
método para suscribirse: si necesita ejecutar algo solo la primera vez que se activa un evento:
timerEventEmitter.once('update', (time) => {
console.log('Message Received from publisher');
console.log(`${time} seconds passed since the program started`);
});
Ejecutar este código producirá:
Message Received from publisher
1 seconds passed since the program started
EventEmitter con varios oyentes
Ahora, hagamos un tipo diferente de emisor de eventos con tres oyentes. Esta será una cuenta regresiva. Un oyente actualizará al usuario en cada segundo, un oyente notificará al usuario cuando la cuenta regresiva se acerca a su fin y el último oyente se activará una vez que la cuenta regresiva haya terminado:
update
– Este evento se activará cada segundoend
– Este evento se activará al final de la cuenta regresiva.end-soon
– Este evento se activará 2 segundos antes de que finalice la cuenta atrás.
Creemos una función que cree este emisor de eventos y lo devuelva:
const countDown = (countdownTime) => {
const eventEmitter = new EventEmitter();
let currentTime = 0;
// This will trigger the update event each passing second
const timer = setInterval(() => {
currentTime++;
eventEmitter.emit('update', currentTime);
// Check if countdown has reached to the end
if (currentTime === countdownTime) {
clearInterval(timer);
eventEmitter.emit('end');
}
// Check if countdown will end in 2 seconds
if (currentTime === countdownTime - 2) {
eventEmitter.emit('end-soon');
}
}, 1000);
return eventEmitter;
};
En esta función, hemos iniciado un evento basado en intervalos que emite el update
evento en un intervalo de un segundo.
En la primera if
condición, verificamos si la cuenta regresiva ha llegado al final y detenemos el evento basado en intervalos. Si es así, disparamos un end
evento.
En la segunda condición, verificamos si la cuenta regresiva está a 2 segundos de finalizar y publicamos el end-soon
evento si es así.
Ahora, agreguemos algunos suscriptores a este emisor de eventos:
Te puede interesar:Git: ignorar archivos con .gitignoreconst myCountDown = countDown(5);
myCountDown.on('update',
console.log(`${t} seconds since the timer started`);
});
myCountDown.on('end', () => {
console.log('Countdown is completed');
});
myCountDown.on('end-soon', () => {
console.log('Count down will end in 2 seconds');
});
Este código debería producir:
1 seconds has been passed since the timer started
2 seconds has been passed since the timer started
3 seconds has been passed since the timer started
Count down will end in 2 seconds
4 seconds has been passed since the timer started
5 seconds has been passed since the timer started
Countdown is completed
Extensión de EventEmitter
En esta sección, hagamos un emisor de eventos con la misma funcionalidad, extendiendo el EventEmitter
clase. Primero, crea un CountDown
clase que manejará los eventos:
const { EventEmitter } = require('events');
class CountDown extends EventEmitter {
constructor(countdownTime) {
super();
this.countdownTime = countdownTime;
this.currentTime = 0;
}
startTimer() {
const timer = setInterval(() => {
this.currentTime++;
this.emit('update', this.currentTime);
// Check if countdown has reached to the end
if (this.currentTime === this.countdownTime) {
clearInterval(timer);
this.emit('end');
}
// Check if countdown will end in 2 seconds
if (this.currentTime === this.countdownTime - 2) {
this.emit('end-soon');
}
}, 1000);
}
}
Como puede ver, podemos usar this.emit()
dentro de la clase directamente. También el startTimer()
La función se utiliza para permitirnos controlar cuándo comienza la cuenta atrás. De lo contrario, comenzaría tan pronto como se cree el objeto.
Creemos un nuevo objeto de CountDown
y suscríbete a él:
const myCountDown = new CountDown(5);
myCountDown.on('update',
console.log(`${t} seconds has been passed since the timer started`);
});
myCountDown.on('end', () => {
console.log('Countdown is completed');
});
myCountDown.on('end-soon', () => {
console.log('Count down will be end in 2 seconds');
});
myCountDown.startTimer();
Ejecutar esto resultará en:
1 seconds has been passed since the timer started
2 seconds has been passed since the timer started
3 seconds has been passed since the timer started
Count down will be end in 2 seconds
4 seconds has been passed since the timer started
5 seconds has been passed since the timer started
Countdown is completed
Un alias para el on()
la función es addListener()
. Considera el end-soon
oyente de eventos:
myCountDown.on('end-soon', () => {
console.log('Count down will be end in 2 seconds');
});
Podríamos haber hecho lo mismo con addListener()
Me gusta esto:
myCountDown.addListener('end-soon', () => {
console.log('Count down will be end in 2 seconds');
});
Ambos trabajan. Son casi como sinónimos. Sin embargo, la mayoría de los codificadores prefieren usar on()
.
Funciones importantes de EventEmitter
Echemos un vistazo a algunas de las funciones importantes que podemos usar en EventEmitter
s.
eventNames ()
Esta función devolverá todos los nombres de oyentes activos como una matriz:
const myCountDown = new CountDown(5);
myCountDown.on('update',
console.log(`${t} seconds has been passed since the timer started`);
});
myCountDown.on('end', () => {
console.log('Countdown is completed');
});
myCountDown.on('end-soon', () => {
console.log('Count down will be end in 2 seconds');
});
console.log(myCountDown.eventNames());
Ejecutar este código resultará en:
[ 'update', 'end', 'end-soon' ]
Si nos suscribiéramos a otro evento como myCount.on('some-event', ...)
, el nuevo evento también se agregará a la matriz.
Tenga en cuenta que este método no devuelve los eventos publicados. Devuelve una lista de eventos a los que está suscrito.
removeListener ()
Como sugiere el nombre, esta función elimina un controlador suscrito de un EventEmitter
:
const { EventEmitter } = require('events');
const emitter = new EventEmitter();
const f1 = () => {
console.log('f1 Triggered');
}
const f2 = () => {
console.log('f2 Triggered');
}
emitter.on('some-event', f1);
emitter.on('some-event', f2);
emitter.emit('some-event');
emitter.removeListener('some-event', f1);
emitter.emit('some-event');
Después de que se activa el primer evento, ya que ambos f1
y f2
están activos: se ejecutarán ambas funciones. Después de eso, hemos eliminado f1
desde el EventEmitter
. Cuando emitimos el evento nuevamente, solo f2
ejecutará:
f1 Triggered
f2 Triggered
f2 Triggered
Un alias para removeListener()
es off()
. Por ejemplo, podríamos haber escrito:
emitter.removeListener('some-event', f1);
Como:
emitter.off('some-event', f1);
Ambos tienen el mismo efecto.
removeAllListeners ()
Nuevamente, como sugiere el nombre, esta función eliminará a todos los oyentes de todos los eventos de un EventEmitter
:
const { EventEmitter } = require('events');
const emitter = new EventEmitter();
const f1 = () => {
console.log('f1 Triggered');
}
const f2 = () => {
console.log('f2 Triggered');
}
emitter.on('some-event', f1);
emitter.on('some-event', f2);
emitter.emit('some-event');
emitter.removeAllListeners();
emitter.emit('some-event');
El primero emit()
disparará ambos f1
y f2
ya que están activos en ese momento. Después de quitarlos, el emit()
La función emitirá el evento, pero ningún oyente responderá a él:
f1 Triggered
f2 Triggered
Manejo de errores
Si desea emitir un error con su EventEmitter
, debe hacerse con un error
nombre del evento. Esto es estándar para todos EventEmitter
objetos en Node.js. Este evento también debe ir acompañado de un Error
objeto. Por ejemplo, se puede emitir un evento de error como este:
myEventEmitter.emit('error', new Error('Something bad happened'));
Cualquier oyente del error
El evento debe tener una devolución de llamada con un argumento para capturar el Error
objeto y manipularlo con gracia. Si una EventEmitter
emite un error
evento, pero no hay oyentes suscritos a error
eventos, el programa Node.js lanzaría el Error
que fue emitido.
Esto finalmente detendrá la ejecución del proceso Node.js y saldrá de su programa, mientras muestra el seguimiento de la pila para el error en la consola.
Asumamos, en nuestro CountDown
clase, la countdownTime
El parámetro no puede comenzar siendo menor que 2 porque no podremos activar el evento. end-soon
de otra manera.
En tal caso, emitamos un error
evento:
class CountDown extends EventEmitter {
constructor(countdownTime) {
super();
if (countdownTimer < 2) {
this.emit('error', new Error('Value of the countdownTimer cannot be less than 2'));
}
this.countdownTime = countdownTime;
this.currentTime = 0;
}
// ...........
}
El manejo de este error se maneja de la misma manera que otros eventos:
myCountDown.on('error', (err) => {
console.error('There was an error:', err);
});
Se considera una buena práctica tener siempre un oyente para error
eventos.
Módulos nativos que usan EventEmitter
Muchos módulos nativos en Node.js extienden la EventEmitter
class y, por tanto, son emisores de eventos.
Un gran ejemplo es el Stream
clase. La documentación oficial dice:
Las transmisiones pueden ser legibles, de escritura o ambas. Todas las corrientes son instancias de EventEmitter
.
Echemos un vistazo a algunos clásicos Stream
uso:
const fs = require('fs');
const writer = fs.createWriteStream('example.txt');
for (let i = 0; i < 100; i++) {
writer.write(`hello, #${i}!n`);
}
writer.on('finish', () => {
console.log('All writes are now complete.');
});
writer.end('This is the endn');
Sin embargo, entre la operación de escritura y la writer.end()
llamada, hemos agregado un oyente. Stream
s emiten un finished
evento al finalizar. Otros eventos, como error
, pipe
y unpipe
se emiten cuando ocurre un error o un flujo de lectura se canaliza o no se envía desde un flujo de escritura.
Otra clase notable es la child_process
clase y su spawn()
método:
const { spawn } = require('child_process');
const ls = spawn('ls', ['-lh', '/usr']);
ls.stdout.on('data', (data) => {
console.log(`stdout: ${data}`);
});
ls.stderr.on('data', (data) => {
console.error(`stderr: ${data}`);
});
ls.on('close', (code) => {
console.log(`child process exited with code ${code}`);
});
Cuando el child_process
escribe en la tubería de salida estándar, el data
evento del stdout
(lo cual también extends EventEmitter
) disparará. Cuando el flujo de salida encuentra un error, el data
El evento se envía desde el stderr
tubo.
Finalmente, una vez finalizado el proceso, el close
evento se dispara.
Conclusión
La arquitectura basada en eventos nos permite crear sistemas desacoplados pero altamente cohesivos. Los eventos representan el resultado de una determinada acción y 1..n
los oyentes pueden definirse para escucharlos y reaccionar ante ellos.
En este artículo, nos sumergimos en el EventEmitter
clase y su funcionalidad. Lo hemos instanciado y usado directamente, así como también extendido su comportamiento en un objeto personalizado.
Finalmente, hemos cubierto algunas funciones notables de la clase.
Como siempre, el código fuente está disponible en GitHub.