Grande parte do funcionamento do Node.js é orientada a eventos. Servidores HTTP, streams, sockets, processos e diversos módulos nativos avisam que algo aconteceu por meio de eventos. O EventEmitter no Node.js permite aplicar o mesmo modelo dentro da sua aplicação, criando componentes que publicam acontecimentos sem conhecer diretamente todos os consumidores.
Essa separação ajuda a reduzir acoplamento. Um serviço pode informar que um pedido foi criado, enquanto módulos independentes registram auditoria, atualizam métricas ou disparam notificações. Porém, eventos também podem esconder dependências, acumular listeners e dificultar o tratamento de erros quando são usados sem um contrato claro.
Neste guia, você aprenderá a criar um emissor, registrar listeners com on() e once(), remover assinaturas, tratar o evento especial error, usar símbolos, aguardar eventos com Promises, limitar listeners e testar fluxos orientados a eventos.
O que é EventEmitter?
EventEmitter é uma classe do módulo nativo node:events. Uma instância mantém uma lista de eventos e funções associadas. Quando o método emit() é chamado, os listeners registrados para aquele nome são executados.
const { EventEmitter } = require('node:events');
const emitter = new EventEmitter();
emitter.on('message', message => {
console.log('Mensagem recebida:', message);
});
emitter.emit('message', 'Olá, Node.js');O nome do evento pode ser uma string ou um símbolo. Os argumentos passados a emit() são encaminhados aos listeners na mesma ordem.
A documentação oficial do módulo events detalha a classe, os métodos auxiliares e o comportamento dos listeners. Para revisar a plataforma, consulte o que é Node.js e o que é JavaScript.
Criando uma classe orientada a eventos
Uma classe pode herdar de EventEmitter para publicar mudanças de estado:
const { EventEmitter } = require('node:events');
class OrderService extends EventEmitter {
async createOrder(input) {
const order = await database.orders.create(input);
this.emit('order:created', {
orderId: order.id,
customerId: order.customerId
});
return order;
}
}Agora outros componentes podem reagir ao evento:
const orders = new OrderService();
orders.on('order:created', event => {
auditLogger.info(event, 'Pedido criado');
});
orders.on('order:created', event => {
metrics.increment('orders.created');
});O serviço não precisa importar o logger de auditoria nem o sistema de métricas. Ele apenas publica um fato. Ainda assim, todos os listeners são executados no mesmo processo e, por padrão, de maneira síncrona durante a chamada de emit().
Listeners são chamados de forma síncrona
Quando um evento é emitido, os listeners registrados são chamados na ordem de inscrição. O método emit() só retorna depois que todos terminam, salvo quando o listener inicia uma tarefa assíncrona e não é aguardado.
emitter.on('task', () => {
console.log('primeiro');
});
emitter.on('task', () => {
console.log('segundo');
});
console.log('antes');
emitter.emit('task');
console.log('depois');A saída será: antes, primeiro, segundo e depois. Um listener pesado bloqueia o restante da execução. Para trabalho intensivo de CPU, use estratégias como Worker Threads no Node.js. Para tarefas duráveis ou distribuídas, considere uma fila, como apresentado no guia de Redis com Node.js.
Usando once() para ouvir apenas uma vez
once() registra um listener removido automaticamente depois da primeira execução:
emitter.once('connected', connection => {
console.log('Conectado:', connection.id);
});
emitter.emit('connected', { id: 1 });
emitter.emit('connected', { id: 2 });Somente o primeiro evento será processado. Esse método é adequado para inicialização, conclusão de uma operação ou qualquer acontecimento esperado uma única vez.
Removendo listeners
Para remover um listener, mantenha a referência da função usada no registro:
function handleUpdate(payload) {
console.log(payload);
}
emitter.on('update', handleUpdate);
emitter.off('update', handleUpdate);Também existe removeListener(), que possui finalidade equivalente. Evite criar uma função anônima no registro quando você precisará removê-la depois, pois uma nova função não corresponde à referência original.
const listener = payload => console.log(payload);
emitter.on('update', listener);
try {
await runOperation();
} finally {
emitter.off('update', listener);
}O bloco finally garante a limpeza mesmo quando a operação falha.
O evento especial error
O evento error exige atenção especial. Se um EventEmitter emitir error sem possuir listener correspondente, o Node.js lança o erro e o processo pode ser encerrado.
emitter.on('error', error => {
console.error('Falha no componente:', error);
});
emitter.emit('error', new Error('Conexão perdida'));Não use o listener apenas para esconder falhas. Registre contexto, atualize métricas e decida se a operação pode continuar. Em bibliotecas, documente claramente quais erros são emitidos e quando.
Eventos assíncronos e Promises
O emit() não aguarda automaticamente uma função async:
emitter.on('save', async data => {
await database.save(data);
});
emitter.emit('save', payload);Nesse exemplo, a chamada retorna antes que a gravação termine. Uma rejeição também pode se tornar não tratada. Quando o resultado faz parte da operação principal, chamar diretamente uma função assíncrona costuma ser mais previsível.
Eventos são melhores para observadores independentes, telemetria e notificações internas. Para uma sequência obrigatória de etapas, prefira chamadas explícitas ou uma pipeline com tratamento claro de erros.
Aguardando um evento com events.once()
O módulo oferece uma função que transforma a próxima ocorrência em Promise:
const { once } = require('node:events');
async function waitForReady(server) {
const [details] = await once(server, 'ready');
return details;
}A Promise resolve com um array contendo os argumentos emitidos. Se o emissor publicar um evento error antes do evento esperado, a Promise normalmente é rejeitada.
Consumindo eventos como iterador assíncrono
Para fluxos contínuos, events.on() permite consumir eventos com for await:
const { on } = require('node:events');
async function consume(emitter, signal) {
for await (const [message] of on(emitter, 'message', { signal })) {
console.log('Nova mensagem:', message);
}
}O sinal permite encerrar o consumo. O artigo sobre AbortController no Node.js explica como combinar cancelamento, timeout e desligamento controlado.
Limite de listeners e avisos de memória
O Node.js emite um aviso quando muitos listeners são adicionados ao mesmo evento. Esse aviso não significa necessariamente vazamento, mas indica que o padrão merece revisão.
emitter.setMaxListeners(20);Aumentar o limite não corrige um vazamento. Primeiro, descubra por que cada requisição, tentativa ou reconexão adiciona um novo listener. Verifique se os listeners são removidos quando deixam de ser necessários.
Inspecionando listeners
Durante testes e diagnóstico, métodos como listenerCount(), eventNames() e listeners() ajudam a inspecionar o emissor:
console.log(emitter.eventNames());
console.log(emitter.listenerCount('message'));
console.log(emitter.listeners('message'));Evite usar essa introspecção para criar dependências frágeis em produção. Ela é mais útil em testes, ferramentas internas e investigação de vazamentos.
prependListener() e ordem de execução
prependListener() adiciona uma função no início da lista:
emitter.prependListener('request', request => {
validateRequest(request);
});Embora seja útil em infraestrutura, depender excessivamente da ordem de listeners torna o fluxo difícil de entender. Quando uma etapa precisa obrigatoriamente acontecer antes de outra, uma composição explícita costuma ser melhor.
Usando símbolos como nomes
Símbolos evitam colisões entre eventos internos e públicos:
const INTERNAL_FLUSH = Symbol('internalFlush');
emitter.on(INTERNAL_FLUSH, () => {
flushBuffers();
});Essa estratégia é útil dentro de bibliotecas, mas eventos que fazem parte da API pública geralmente são mais fáceis de documentar como strings.
Definindo um contrato de eventos
Um bom evento possui nome, payload e comportamento previsíveis. Documente:
- quando o evento é emitido;
- quais campos o payload contém;
- se pode ocorrer mais de uma vez;
- se o listener pode lançar erros;
- se a ordem entre listeners importa;
- quais dados são seguros para logs;
- como cancelar ou remover a assinatura.
Prefira nomes que descrevam fatos, como order:created ou connection:closed, em vez de comandos ambíguos. O produtor anuncia o que aconteceu; o consumidor decide como reagir.
EventEmitter não substitui uma fila
Eventos locais desaparecem quando o processo encerra. Eles não oferecem persistência, confirmação, repetição automática ou distribuição entre servidores. Se uma ação precisa sobreviver a falhas, ser executada por outro processo ou possuir retries, use uma fila ou broker.
O EventEmitter é adequado para comunicação interna e efêmera. Para atualizações em rede, avalie HTTP, WebSocket ou Server-Sent Events. Para conhecer comunicação em tempo real, consulte WebSocket com Node.js.
Como testar eventos
Um teste pode aguardar o evento e verificar o payload:
const assert = require('node:assert/strict');
const { once } = require('node:events');
const received = once(service, 'order:created');
const order = await service.createOrder(input);
const [event] = await received;
assert.equal(event.orderId, order.id);Registre a espera antes de executar a ação para não perder um evento síncrono. Inclua testes para emissão única, múltiplos listeners, remoção, erro e limpeza depois da conclusão.
Erros comuns
- Não tratar error: uma emissão sem listener pode encerrar o processo.
- Adicionar listeners repetidamente: memória e processamento crescem a cada operação.
- Esperar que emit aguarde async: Promises de listeners não são coordenadas automaticamente.
- Usar eventos para fluxo obrigatório: dependências importantes ficam escondidas.
- Aumentar maxListeners sem investigar: o aviso desaparece, mas o vazamento continua.
- Emitir payloads mutáveis compartilhados: um listener pode alterar dados observados pelos demais.
- Usar evento local para trabalho durável: a tarefa se perde quando o processo falha.
Boas práticas para produção
- Use nomes consistentes e payloads pequenos.
- Documente eventos públicos.
- Trate sempre o evento
error. - Use
once()para ocorrências únicas. - Remova listeners no encerramento da operação.
- Não execute trabalho pesado dentro de listeners síncronos.
- Não dependa da ordem quando ela não estiver explicitamente documentada.
- Monitore quantidade de listeners e duração dos handlers.
- Use filas para processamento persistente.
- Teste concorrência, erros e reconexões.
Conclusão
O EventEmitter no Node.js é uma ferramenta simples e poderosa para desacoplar componentes dentro do mesmo processo. Com on(), once(), emit() e métodos de remoção, serviços podem publicar acontecimentos e permitir que diferentes módulos reajam sem dependências diretas.
O ganho de flexibilidade exige disciplina. Trate erros, limpe listeners, não esconda sequências obrigatórias e não confunda eventos locais com mensageria durável. Com contratos claros e testes adequados, o modelo orientado a eventos melhora a organização sem transformar o fluxo da aplicação em uma rede difícil de acompanhar.




