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


---

# Agent Instructions: Querying This Documentation

If you need additional information that is not directly available in this page, you can query the documentation dynamically by asking a question.

Perform an HTTP GET request on the current page URL with the `ask` query parameter:

```
GET https://docs.guiseek.dev/snippets/event-emitter.md?ask=<question>
```

The question should be specific, self-contained, and written in natural language.
The response will contain a direct answer to the question and relevant excerpts and sources from the documentation.

Use this mechanism when the answer is not explicitly present in the current page, you need clarification or additional context, or you want to retrieve related documentation sections.
