Event Emitter
Como criar um gerenciador de eventos
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:
on
: espera o nome do evento e uma função callback, que será executada toda vez que houver uma emissão para o evento.once
: espera o nome do evento e uma função callback, que será executada apenas uma vez para o evento.off
: espera o nome do evento e a função callback que não será mais executada para o evento.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
.
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.
export class EventEmitter<T> {
#listeners = new Map()
#getListeners<K extends keyof T>(type: K): Set<Callback<T[K]>> {
return this.#listeners.get(type) ?? new Set()
}
}
Perceba que #listeners
é um Map
que registra um Set
de Callback
para cada evento.
off
off
O método off
será utilizado por quase todos os demais.
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
on
Geralmente este é o método mais utilizado.
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
once
Aqui precisamos definir a flag que determina quantas vezes a função será executada.
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
emit
Vamos para nosso último e 2º principal método.
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
.
Código completo
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
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.
Last updated
Was this helpful?