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 鈥嬧媝or 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.

    Deja una respuesta

    Tu direcci贸n de correo electr贸nico no ser谩 publicada. Los campos obligatorios est谩n marcados con *