Manejo de eventos en Node.js con EventEmitter

    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:

    Te puede interesar:Conversión de devoluciones de llamada en promesas en Node.js

    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ón
    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:

    Te puede interesar:Git: ignorar archivos con .gitignore
    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.

    Te puede interesar:Construyendo una API GraphQL con Django

    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.

    Te puede interesar:Obtener cadenas de consulta y parámetros en Express.js

    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.

    Te puede interesar:Cargar archivos con Spring Boot

    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.

    Rate this post