Padrões EventEmitter
Use EventEmitter do Node.js para padrões publish-subscribe, com eventos tipados e manipuladores assíncronos.
Busque em todas as páginas da documentação
Use EventEmitter do Node.js para padrões publish-subscribe, com eventos tipados e manipuladores assíncronos.
🤖 Read the SystemsArchitect.io Blog for over 100+ cloud architecture articles 🔥
Cartão de receita de referência rápida - pronto para copiar e colar.
import { EventEmitter } from "node:events";
const emitter = new EventEmitter();
// Assinar
emitter.on("message", (text: string) => {
console.log("recebido:", text);
});
// Assinar uma vez
emitter.once("ready", () => {
console.log("ready disparado uma vez");
});
// Cancelar assinatura
const handler = (n: number) => console.log(n);
emitter.on("tick", handler);
emitter.off("tick", handler);
// Publicar
emitter.emit("message", "olá");
emitter.emit("ready");
// Sempre trate erros - 'error' não tratado derruba o processo
emitter.on("error", (err) => {
console.error("erro do emitter:", err);
});
emitter.emit("error", new Error("boom"));Quando usar isso: Desacoplar produtores de consumidores dentro de um único processo Node - filas de jobs, pipelines de log, ciclo de vida de streams, hooks de plugins.
Uma classe wrapper EventEmitter tipada para uma fila de jobs.
import { EventEmitter } from "node:events";
// Mapeamento de tipos: nome do evento para tupla de argumentos
interface JobEvents {
"job:start": [id: string];
"job:progress": [id: string, percent: number];
"job:complete": [id: string, result: unknown];
"job:error": [id: string, err: Error];
}
class JobQueue extends EventEmitter {
override on<K extends keyof JobEvents>(
event: K,
listener: (...args: JobEvents[K]) => void
): this {
return super.on(event, listener as (...args: unknown[]) => void);
}
override emit<K extends keyof JobEvents>(
event: K,
...args: JobEvents[K]
): boolean {
return super.emit(event, ...args);
}
async run(id: string, task: (report: (p: number) => void) => Promise<unknown>) {
this.emit("job:start", id);
try {
const result = await task((percent) => {
this.emit("job:progress", id, percent);
});
this.emit("job:complete", id, result);
} catch (err) {
this.emit("job:error", id, err as Error);
}
}
}
const queue = new JobQueue();
queue.on("job:start", (id) => console.log(`[${id}] iniciando`));
queue.on("job:progress", (id, pct) => console.log(`[${id}] ${pct}%`));
queue.on("job:complete", (id, result) => console.log(`[${id}] concluído`, result));
queue.on("job:error", (id, err) => console.error(`[${id}] falhou`, err));
await queue.run("job-1", async (report) => {
for (let i = 0; i <= 100; i += 25) {
report(i);
await new Promise((r) => setTimeout(r, 50));
}
return { ok: true };
});O que isso demonstra:
on e emit fornecem autocompletar completo e segurança de payload.run) e os consumidores (os manipuladores on) são totalmente desacoplados.EventEmitter mantém um mapa interno do nome do evento para um array de funções de listener. emit itera esse array de forma síncrona, chamando cada listener na ordem em que foi registrado. Não há fila embutida nem backpressure - listeners rodam no tick atual, a menos que eles mesmos agendem trabalho assíncrono.
on anexa, once anexa um wrapper que se remove após a primeira chamada, off (alias removeListener) remove por referência, e removeAllListeners(event?) limpa tudo.
O evento 'error' é especial: se nada estiver ouvindo quando você emit('error', err), o Node lança o erro e, se não for capturado, termina o processo.
EventEmitter Tipado com genéricos. O padrão JobQueue acima é a abordagem idiomática em Node + TypeScript moderno. Bibliotecas como tsee ou typed-emitter empacotam a mesma ideia.
Iteração assíncrona com events.on(). Consuma eventos como um stream:
import { on, EventEmitter } from "node:events";
const emitter = new EventEmitter();
setTimeout(() => emitter.emit("data", 1), 10);
setTimeout(() => emitter.emit("data", 2), 20);
for await (const [value] of on(emitter, "data")) {
console.log("valor:", value);
if (value === 2) break;
}once como uma Promise. Ótimo para esperar por um único evento de ciclo de vida:
import { once, EventEmitter } from "node:events";
const emitter = new EventEmitter();
setTimeout(() => emitter.emit("ready", "ok"), 50);
const [value] = await once(emitter, "ready");
console.log(value); // "ok"Estendendo EventEmitter. Crie uma subclasse quando o emitter for a identidade principal do seu objeto (o exemplo JobQueue). Componha (tenha um #emitter interno) quando os eventos forem uma preocupação secundária.
Removendo todos os listeners. emitter.removeAllListeners() sem argumento destrói todos os eventos; com um argumento, apenas aquele evento. Útil no teardown, mas perigoso se você não for o dono do emitter.
Aviso de max listeners. Node imprime MaxListenersExceededWarning em 11 listeners por evento. Aumente com emitter.setMaxListeners(50) ou globalmente via EventEmitter.defaultMaxListeners. O aviso é uma dica de vazamento, não um erro.
Defina um mapa de tipos de evento como uma interface onde cada chave é o nome do evento e cada valor é uma tupla dos argumentos do listener - por exemplo interface Events \{ start: [id: string]; progress: [percent: number] \}. Use este mapa com uma classe base genérica como TypedEmitter<Events> (de typed-emitter), ou escreva suas próprias sobrescritas de on, once, off e emit que restrinjam o nome do evento com K extends keyof Events e os args com Events[K]. Isso lhe dará autocompletar nos nomes de eventos e verificação em tempo de compilação das formas dos payloads.
error derruba o processo. Emitir 'error' sem listener lança uma exceção. Sempre anexe um listener de error antes de qualquer caminho de código que possa emitir um.emit é síncrono. Listeners rodam na ordem de registro no mesmo tick. Um listener síncrono lento bloqueia todos os outros listeners e o event loop.once não impede outras inscrições. Ele apenas envolve aquele manipulador. Outros listeners on continuam disparando para sempre.emitter.off("x", () => {}) passa uma referência de função totalmente nova e não remove nada. Armazene o manipulador em uma variável se você precisar removê-lo.emit e pode derrubar listeners não relacionados. Envolva trabalho arriscado em try/catch ou use manipuladores assíncronos que rejeitem para um evento error.emit não espera por nada. Promises rejeitadas em listeners se tornam rejeições não tratadas, a menos que você anexe .catch dentro do listener.| Opção | Melhor Para | Notas |
|---|---|---|
node:events EventEmitter | Serviços apenas Node | Embutido, sem dependências, emit síncrono. |
EventTarget / CustomEvent | Universal (Node 19+, navegadores, workers) | Padrão da Web, um pouco mais verboso, suporta AbortSignal para limpeza. |
mitt | Pub-sub universal minúsculo | ~200 bytes, sem wildcard, sem once - apenas on/off/emit. |
nanoevents | Emitter tipado pequeno | Genéricos TypeScript de primeira classe, retorna uma função de cancelamento. |
RxJS Subject | Streams, operadores, backpressure | Pesado, mas incomparável para pipelines assíncronos complexos. |
| Streams Node | Streams de bytes/objetos com backpressure | Use quando ordem e controle de fluxo importam, não apenas sinalização. |
error sem listener?Node lança o erro de forma síncrona a partir de emit. Se nada o capturar, o processo trava com uma mensagem Unhandled 'error' event. Sempre anexe um listener de error antes do primeiro emit.
emit é síncrono ou assíncrono?Síncrono. Listeners rodam na ordem de registro no tick atual, bloqueando o event loop até que retornem. Listeners assíncronos são "dispare e esqueça" - emit não os aguarda.
once?Salve a referência e passe-a para off: const h = () => {}; emitter.once("x", h); emitter.off("x", h). Você também pode chamar removeAllListeners("x") se você for o dono do emitter.
MaxListenersExceededWarning dispara em 11 listeners em um único evento - uma heurística para detectar vazamentos. Se você legitimamente precisar de mais, chame emitter.setMaxListeners(50) ou defina EventEmitter.defaultMaxListeners globalmente.
Defina uma interface mapeando nomes de eventos para tuplas de argumentos, então crie uma subclasse de EventEmitter e sobrescreva on e emit com assinaturas genéricas restritas por K extends keyof YourEvents. Veja o exemplo de trabalho JobQueue nesta página.
Estenda quando eventos forem a identidade principal do objeto (um barramento, uma fila, um stream). Componha com um campo privado #emitter quando eventos forem uma preocupação secundária - isso mantém sua API pública menor e evita expor todos os métodos de EventEmitter.
await em um evento?Sim - use events.once(emitter, "name") que retorna uma promise resolvendo para os argumentos emitidos como um array. Ele também rejeita em 'error', o que o torna mais seguro do que um wrapper feito manualmente.
Use events.on(emitter, "name") que retorna um iterador assíncrono. Você pode usar for await sobre ele e break para parar de ouvir. Emparelhe com uma opção AbortSignal para cancelamento limpo.
off e removeListener?Nenhuma - off é um alias adicionado para paridade com o DOM. Ambos removem um listener por referência.
emitter.off("x", () => {}) cria uma função totalmente nova e a passa para off, que não encontra correspondência. Armazene o manipulador em uma variável ou use once se você só precisar dele uma vez.
EventTarget com CustomEvent é o padrão da web e funciona em todos os lugares, incluindo Node 19+. Para pub-sub universal ultra-pequeno, use mitt ou nanoevents. Não envie node:events para o navegador.
Envolva o corpo assíncrono em um try/catch e encaminhe falhas para o emitter: emitter.on("x", async (v) => { try { await work(v); } catch (err) { emitter.emit("error", err); } }). emit não aguardará seu manipulador, então você deve capturar dentro dele.
docs/nodejs-scripts/http-server.md - Servidores HTTP, que são eles mesmos instâncias de EventEmitter.docs/browser-apis/postmessage-basics.md - mensagens entre janelas, um padrão pub-sub do lado do navegador.docs/react-hooks/use-effect.md - assinando e limpando listeners de eventos do React.Revisado por Chris St. John·Última atualização: 16 de jul. de 2026
🤖 Read the SystemsArchitect.io Blog for over 100+ cloud architecture articles 🔥