NodeJS Event Emitter
Tags: node, eventemitter, javascript
Published: 2021-05-24 00:45:43

## O que é o Event Emitter?

Event Emitter é um módulo do NodeJS que nos ajuda a criar um padrão de *publisher-subscriber.*

Com um Event Emitter podemos simplesmente criar um evento de qualquer parte de uma aplicação, escutar este evento e tomar alguma ação baseado nele.

![Alt Text](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/8nqfo9tfbfl9h5ec7yjl.png)

## Criando um Event Emitter

Para criar um event emitter é preciso instanciar o `EventEmitter` do módulo `events` no NodeJS.

```jsx
const { EventEmitter } = require('events');

const eventEmitter = new EventEmitter();
```

Esse é o básico para criar um event emitter. Mas o que podemos fazer com ele? 🤔

## Publicando eventos e ouvindo-os

A classe do EventEmitter têm uma série de métodos e podemos usá-los para publicar e ouvir eventos. Por hora, focaremos em dois deles:

- `emit(eventName...)`
- `on(eventName...)`

Para publicar um evento nós utilizamos o método `emit()`. Já para ouvir, utilizamos o método `on()`. E a forma como fazemos isso utilizando EventEmitters é por meio de "nomes".

Continuando o código acima onde instanciamos a classe EventEmitter, agora podemos registrar um evento para posteriormente escutá-lo.

```jsx
const { EventEmitter } = require('events');

const eventEmitter = new EventEmitter();

// Escuta o evento
eventEmitter.on('meuEvento', () => {
	console.log('Dados recebidos!');
});

// Emite o evento
eventEmitter.emit('meuEvento');
```

Output ao rodar o código: `> Dados recebidos`

No exemplo acima, na última linha de código, criamos um evento com o nome de "meuEvento"*.* E temos um *listener* (algo capaz de escutar o evento, que nesse caso é o método `on`) algumas linhas de código acima da publicação do evento.

No momento que publicamos um evento, é preciso que já exista um listener para nosso evento que foi publicado. Então, se por exemplo, tivermos o seguinte código...

```jsx
const { EventEmitter } = require('events');

const eventEmitter = new EventEmitter();

// Escuta o evento
eventEmitter.on('meuEvento', () => {
	console.log('Listener 1');
});

// Emite o evento
eventEmitter.emit('meuEvento');

// Escuta evento
eventEmitter.on('meuEvento', () => {
	console.log('Listener 2');
});
```

Output ao rodar o código: `> Listener 1`

Temos apenas a primeira escuta sendo executa pois ela foi registrada antes do evento ter sido emitido. Diferente do segundo *listener*, que apenas ocorre após o evento já ter sido publicado e com isso não é executado.

## Uma instância do EventEmitter deve ser Singleton para um único nome de evento

Em outras palavras, tanto o método `on()` quanto o `emit()` devem ser chamados na mesma instância do EventEmitter. Se registramos o evento em uma instância e tentarmos escutá-los em outra instância, não irá funcionar. 

```jsx
const { EventEmitter } = require('events');

// Primeira instância
const eventEmitter1 = new EventEmitter();
eventEmitter1.on('meuEvento', () => {
	console.log('Listener');
});

// Segunda instância
const eventEmitter2 = new EventEmitter();
eventEmitter2.emit('meuEvento');
```

Se tentarmos rodar esse código, não teremos nenhum retorno, pois existem duas instâncias separadas sendo utilizadas: **uma para registrar o evento e a outra para escutar ele.**

## Manter uma única instância do EventEmitter para toda a aplicação

Se não podemos ter mais de uma instância do EventEmitter para um mesmo nome de evento, como podemos fazer para uma aplicação completa trabalhar com apenas uma única criação?

Para isso, existe uma estratégia para criar e manter uma cópia única de uma instância caso estejamos utilizando o `express`, por exemplo.

Quando for criar o EventEmitter, podemos guardar sua instância como uma configuração a nível de aplicação usando o `app.set(<key>, <value>)`.

```jsx
const { EventEmitter } = require('events');
const express = require('express');

const eventEmitter = new EventEmitter();

const app = express();
app.set('eventEmitter', eventEmitter);

// Acessamos a partir de qualquer módulo da aplicação
console.log(app.get('eventEmitter'));
```

Outra alternativa é criar um módulo responsável por instanciar a classe do EventEmitter e exportar essa instância para ser utilizada em outras partes da aplicação.

```jsx
const { EventEmitter } = require('events');
const eventEmitter = new EventEmitter();
...
...

module.exports = eventEmitter;
```

```jsx
const eventEmitter = require('./eventEmitter');

// Registra listener
eventEmitter.on('meuEvento', () => {
	console.log('Listener!');
});

// Registra evento
eventEmitter.emit('meuEvento');
```

## Síncrono ou Assíncrono?

O NodeJS é assíncrono, mas como nenhum I/O está envolvido na emissão de um evento, a entrega do evento é tratada de forma síncrona na iteração atual do event loop do NodeJS.

Podemos comprovar isso executando o seguinte código:

```jsx
const { EventEmitter } = require('events');
const eventEmitter = new EventEmitter();

eventEmitter.on('meuEvento', (data) => {
	console.log(data);
});

console.log('Log 1');
eventEmitter.emit('meuEvento', 'Log 2');
console.log('Log 3');
```

Temos como output:

`> Log 1`

`> Log 2`

`> Log 3`

Isso confirma a premissa de que existe uma ordem de execução, sendo que os listeners são executados na ordem em que são criados para um evento. Podemos levar em consideração esse outro exemplo:

```jsx
const { EventEmitter } = require('events');
const eventEmitter = new EventEmitter();

eventEmitter.on('meuEvento', (data) => {
	console.log(data, ' - Primeiro');
});

console.log('Log 1');

eventEmitter.on('meuEvento', (data) => {
	console.log(data, ' - Segundo');
});

eventEmitter.emit('meuEvento', 'Evento emitido');

console.log('Log 2');
```

Onde temos o output:

`> Log 1`

`> Evento emitido - Primeiro`

`> Evento emitido - Segundo`

`> Log 2`

## Como e onde o NodeJS usa internamente o Event Emitter

O NodeJS utiliza internamente eventos em diversos pontos do seu ambiente. Um dos casos de uso são as Streams. Streams são construídos em cima do módulo EventEmitter e possuem eventos pré-definidos, como: `open`, `end` e `data`.

```jsx
const { createReadStream } = require('fs');

let data = '';
const readerStream = createReadStream('./file.txt');

readerStream.on('open', () => {
	console.log('Abrindo evento...');
});

readerStream.on('data', chuck => {
	data += chuck; 
});

readerStream.on('end', () => {
	console.log(data);
	console.log('Finalizando evento');
});
```

Os próprios streams estendem naturalmente e internamente os listeners do event emitter, com isso, não precisamos importar e declarar explicitamente uma nova instância da classe EventEmitter.

Outro exemplo do uso de eventos dentro do Node é o objeto global `process`. O `process` expõe métodos e variáveis que podem emitir eventos e responder por eles.

```jsx
process.on("exit", () => console.log("Saída!"));
process.on('uncaughtException', () => {
    console.log('Exception lançada');
    process.exit();
});
throw new Error('Erro!');
```

## Conclusão

Existem diversos outros métodos da classe EventEmitter que são úteis. Suas aplicações dependem de necessidades específicas, como o `once()` que escuta apenas a primeira ocorrência de um evento emitido e descarta todas as outras. Ou o `eventNames()` que retorna o nome de todos os eventos ativos. A [lista](https://nodejs.org/api/events.html) de funções é bem completa. 🙂

Se tiver curiosidade em tentar entender como seria uma implementação do zero da classe Event Emitter do NodeJS, abaixo deixo um exemplo desenvolvido por mim.

https://stackblitz.com/edit/jp-event-emitter?embed=1&file=index.js