Manejo de eventos en Node.js con EventEmitter

M

Introducción

En este tutorial, vamos a echar un vistazo al nativo de Node EventEmitter clase. Aprenderá sobre eventos, lo que puede hacer con un EvenEmittery 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 EventEmitters 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:

timerEventEmitter.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 segundo
  • end – 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:

const 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 EventEmitters.

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. Streams 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.

About the author

Ramiro de la Vega

Bienvenido a Pharos.sh

Soy Ramiro de la Vega, Estadounidense con raíces Españolas. Empecé a programar hace casi 20 años cuando era muy jovencito.

Espero que en mi web encuentres la inspiración y ayuda que necesitas para adentrarte en el fantástico mundo de la programación y conseguir tus objetivos por difíciles que sean.

Add comment

Sobre mi

Últimos Post

Etiquetas

Esta web utiliza cookies propias y de terceros para su correcto funcionamiento y para fines analíticos y para mostrarte publicidad relacionada con tus preferencias en base a un perfil elaborado a partir de tus hábitos de navegación. Al hacer clic en el botón Aceptar, aceptas el uso de estas tecnologías y el procesamiento de tus datos para estos propósitos. Más información
Privacidad