# Event Emitter

## Introdução

Se você trabalhou com JavaScript, sabe o quanto da interação do usuário é tratada por meio de eventos: cliques do mouse, cliques de botões, entradas do teclado, reações aos movimentos do mouse e assim por diante.

Além disso, também usamos este conceito em vários outros lugares, como WebSocket e até arquiteturas que se baseiam em eventos. Vamos criar nosso próprio gerenciador de eventos e assim integrar onde fizer sentido em nossos projetos.

Para evitar confusão com eventos do DOM, usaremos nomenclaturas utilizadas no EventEmitter do NodeJS.

## Requisitos

Basicamente, nosso EventEmitter será composto por 4 métodos em sua API pública, são eles:

1. `on`: espera o nome do evento e uma função callback, que será executada toda vez que houver uma emissão para o evento.
2. `once`: espera o nome do evento e uma função callback, que será executada apenas uma vez para o evento.
3. `off`: espera o nome do evento e a função callback que não será mais executada para o evento.
4. `emit`: espera o nome do evento e o valor que será emitido para as funções callback ouvindo o evento.

## Implementação

Certo, vamos começar definindo nossa interface `Callback`.

```typescript
export interface Callback<T> {
  (value: T): void
  once?: boolean
}
```

Definimos que `Callback` é uma função e possui uma flag `once` que usaremos para identificar se devemos remover essa função da lista de execuções depois da primeira execução.

Na classe `EventEmitter`, vamos começar definindo nossas propriedades e métodos privados, eles serão utilizados nos métodos da API pública.

<pre class="language-typescript"><code class="lang-typescript"><strong>export class EventEmitter&#x3C;T> {
</strong>  #listeners = new Map()
  
  #getListeners&#x3C;K extends keyof T>(type: K): Set&#x3C;Callback&#x3C;T[K]>> {
    return this.#listeners.get(type) ?? new Set()
  }
}
</code></pre>

Perceba que `#listeners` é um `Map` que registra um `Set` de `Callback` para cada evento.

### `off`

O método `off` será utilizado por quase todos os demais.

```typescript
export class EventEmitter<T> {
  
  off<K extends keyof T>(type: K, callback: Callback<T[K]>) {
    const listeners = this.#getListeners(type)
    listeners.delete(callback)
    this.#listeners.set(type, listeners)
  }
  
}
```

Veja, nós atribuímos o `Set` de um determinado evento em `listeners`, removemos a função solicitada e então sobrescrevemos o evento com o novo `Set`, sem a função.

### `on`

Geralmente este é o método mais utilizado.

```typescript
export class EventEmitter<T> {
  
  on<K extends keyof T>(type: K, callback: Callback<T[K]>) {
    const listeners = this.#getListeners(type)
    this.#listeners.set(type, listeners.add(callback))
    return {off: () => this.off(type, callback)}
  }
  
}
```

Atribuímos o `Set` de um determinado evento em `listeners` e sobrescrevemos o evento com o novo `Set`, com a função adicionada.

### `once`

Aqui precisamos definir a flag que determina quantas vezes a função será executada.

```typescript
export class EventEmitter<T> {
  
  once<K extends keyof T>(type: K, callback: Callback<T[K]>) {
    callback.once = true
    this.on(type, callback)
  }
  
}
```

Repare que apenas adicionamos a flag `once`, e aproveitamos a implementação do método `on`.

### `emit`

Vamos para nosso último e 2º principal método.

```typescript
export class EventEmitter<T> {

  emit<K extends keyof T>(type: K, value: T[K]) {
    const listeners = this.#getListeners(type)
    for (const fn of listeners) {
      if (fn.once) this.off(type, fn)
      fn(value)
    }
  }

}
```

Atribuímos o `Set` de um determinado evento em `listeners` e percorremos para a execução de cada um passando o valor a ser emitido, também removemos a função caso possua a flag `once`.<br>

### Código completo

```typescript
export interface Callback<T> {
  (value: T): void
  once?: boolean
}

export class EventEmitter<T> {
  #listeners = new Map()
  
  on<K extends keyof T>(type: K, callback: Callback<T[K]>) {
    const listeners = this.#getListeners(type)
    this.#listeners.set(type, listeners.add(callback))
    return {off: () => this.off(type, callback)}
  }
  
  once<K extends keyof T>(type: K, callback: Callback<T[K]>) {
    callback.once = true
    this.on(type, callback)
  }
  
  emit<K extends keyof T>(type: K, value: T[K]) {
    const listeners = this.#getListeners(type)
    for (const fn of listeners) {
      if (fn.once) this.off(type, fn)
      fn(value)
    }
  }
  
  off<K extends keyof T>(type: K, callback: Callback<T[K]>) {
    const listeners = this.#getListeners(type)
    listeners.delete(callback)
    this.#listeners.set(type, listeners)
  }
  
  #getListeners<K extends keyof T>(type: K): Set<Callback<T[K]>> {
    return this.#listeners.get(type) ?? new Set()
  }
}
```

## Demo

```typescript
import {EventEmitter} from './event-emitter'

interface EmitterMap {
  update: number
}

const emitter = new EventEmitter<EmitterMap>()

emitter.on('update', (value) => {
  // Usa o value pra algo útil
})

emitter.emit('update', 10)
```

A interface `EmitterMap` pode conter vários eventos, definindo seus respectivos tipos de valores emitidos.

Espero que este conhecimento seja útil pra você, abraço.
