EventEmitter é o padrão que o Node usa para permitir que uma parte de um programa anuncie que algo aconteceu, sem precisar saber quem – se é que alguém – está ouvindo. É uma das APIs mais antigas do módulo node:events do Node e uma das mais fundamentais: streams, servidores HTTP, sockets e processos filhos são todos, por baixo de suas APIs específicas, EventEmitters.
EventEmitter é um mecanismo síncrono de publish/subscribe intraprocessual - um objeto mantém uma lista de eventos nomeados, e qualquer número de funções listener pode se inscrever em cada nome.
Por Que Importa: Ele desacopla o código que detecta algo acontecendo do código que reage a isso, sem que nenhum dos lados precise saber diretamente sobre o outro.
Conceitos Chave:emitter, listener, nome do evento, emit, despacho síncrono, desacoplamento.
Quando Usar: Modelar notificações intraprocessuais (um trabalho concluído, uma conexão fechada, um valor alterado), construir um sistema de plugins/hooks ou trabalhar com qualquer API principal do Node que já exponha eventos (streams, servidores, sockets).
Limitações / Trade-offs: EventEmitter é inteiramente em memória e síncrono por chamada de listener - não tem persistência, entrega entre processos ou durabilidade, e um listener lento ou que lança um erro afeta diretamente o chamador do emitter.
Tópicos Relacionados: API de streams do Node, o loop de eventos e a pilha de chamadas, filas de mensagens, o padrão de design observer.
A ideia central por trás do EventEmitter é o padrão observer: um objeto (o emitter) expõe eventos nomeados, outro código registra funções listener contra esses nomes, e chamar emit(name, ...) executa todos os listeners atualmente registrados para esse nome. Nenhum dos lados precisa de uma referência um ao outro além da própria instância do emitter compartilhada - o emitter não sabe nem se importa quem está ouvindo, e um listener não precisa saber o que o disparou além do nome e da carga útil do evento.
Uma analogia útil: pense em um emitter como o sistema de alarme de incêndio de um prédio em vez de uma ligação telefônica. Puxar o alarme (emit) não disca para uma pessoa específica - ele soa todos os sinos de alarme registrados (listener) no prédio de uma vez, e a pessoa que puxa a alavanca não precisa saber quantas pessoas estão dentro ou quem são elas.
import { EventEmitter } from 'node:events';const uploads = new EventEmitter();uploads.on('completed', (fileId: string) => { console.log(`upload finished: ${fileId}`);});uploads.emit('completed', 'file-123'); // executa todos os listeners de 'completed', em ordem
O Node adotou esse padrão cedo porque muito do que um servidor faz tem uma forma inerentemente de evento: um socket recebe dados, uma requisição é concluída, um processo filho termina. Em vez de inventar uma convenção de callback específica para cada um deles, o Node padronizou um mecanismo compartilhado, e então construiu muitas de suas próprias classes principais diretamente sobre ele.
O fato mecânico mais importante sobre o EventEmitter é que ele é síncrono: chamar emitter.emit(name, ...) invoca cada listener registrado para esse nome, um após o outro, na pilha de chamadas atual, e emit() só retorna após todos eles terem retornado. Isso é fundamentalmente diferente de uma fila de mensagens ou um broker pub/sub - não há buffer, garantia de entrega, e nenhum listener registrado após uma chamada emit() recebe essa emissão específica.
emitter.on('tick', () => console.log('A'));emitter.on('tick', () => console.log('B'));emitter.emit('tick');console.log('C');// Saída: A, B, C - ambos os listeners são executados até a conclusão,// síncronamente, antes de emit() retornar e 'C' ser logado.
Essa síncronia tem uma consequência direta para o tratamento de erros: se um listener lança uma exceção, a exceção se propaga para fora do próprio emit(), assim como qualquer outro throw síncrono - ela não é capturada ou engolida pelo emitter, e interrompe qualquer listener posterior para a mesma emissão de ser executado. Para o evento especial 'error', especificamente, o Node adiciona mais uma regra: se um emitter emite 'error' sem um listener registrado para ele, o Node trata isso como uma exceção não capturada e trava o processo, precisamente porque um erro ignorado silenciosamente é pior do que um erro barulhento.
O outro detalhe mecânico que vale a pena internalizar é por que tantas APIs principais do Node estendem EventEmitter em vez de expor uma forma de callback diferente: dá a cada stream, servidor e socket uma maneira uniforme de expor múltiplos sinais independentemente subscritíveis ('data', 'error', 'close', 'end', e mais) de um único objeto, em vez de precisar de um argumento de construtor ou método separado para cada um. Uma vez que você entende o EventEmitter puro, você já entende a forma de stream.Readable, net.Server e child_process.ChildProcess - eles aplicam nomes de eventos específicos do domínio ao exato mecanismo descrito acima.
A maior limitação estrutural do EventEmitter também é seu trade-off definidor: é inteiramente intraprocessual e em memória. Não há persistência (um listener registrado após um evento disparar nunca o vê), garantia de entrega, ou durabilidade entre processos ou reinícios - um emitter e seus listeners devem viver no mesmo processo em execução, compartilhando o mesmo espaço de memória.
Isso o torna uma ferramenta fundamentalmente diferente de uma fila de mensagens ou um barramento de eventos, mesmo que ambos sejam às vezes chamados genericamente de "eventos". Domain Events vs EventEmitter cobre exatamente onde essa linha se encontra: EventEmitter é adequado para notificações rápidas, intraprocessuais e de melhor esforço (atualizações de estilo UI, hooks internos, sinalização de streams), enquanto filas (Kafka, SQS, RabbitMQ, ou um padrão outbox) são adequadas para qualquer coisa que precise sobreviver a um reinício, cruzar uma fronteira de processo ou garantir entrega pelo menos uma vez.
Abordagem
Força
Fraqueza
Melhor Ajuste
EventEmitter
Overhead quase zero; ordenação síncrona e previsível; embutido em todas as APIs principais relevantes
Apenas intraprocessual; sem persistência; um listener que lança um erro afeta diretamente o chamador do emitter
Notificações intraprocessuais, hooks de plugins, encapsulamento de APIs semelhantes a streams
Fila de mensagens (Kafka, SQS, RabbitMQ)
Durável, sobrevive a reinícios, cruza fronteiras de processos/serviços, garantias de entrega
Overhead operacional e de infraestrutura real; assíncrono por natureza
Eventos entre serviços, qualquer coisa que não deva ser perdida
Callback / Promise direto
Notificação um-para-um mais simples possível; sem abstração extra
Não escala além de um consumidor sem fan-out manual
Um único chamador aguardando um único resultado específico
Dois modos de falha operacionais valem a pena ser mencionados porque aparecem repetidamente em serviços Node em produção. Primeiro, acúmulo de listeners: uma chamada on() sem um off()/removeListener() correspondente mantém esse listener - e tudo o que ele fecha - vivo por toda a vida do emitter, o que é uma fonte rotineira de crescimento de memória em processos de longa duração. O aviso padrão maxListeners do Node (10 por nome de evento) existe especificamente como um detector precoce para isso, não um limite rígido. Vazamentos de Memória de Listeners cobre a limpeza baseada em AbortSignal e quando aumentar esse limite é legítimo versus um sinal de um vazamento real.
Segundo, segurança de tipo em escala: as assinaturas emit/on de um EventEmitter puro aceitam qualquer string como nome de evento e quaisquer argumentos como carga útil, o que significa que um nome de evento digitado incorretamente ou uma forma de carga útil incompatível são invisíveis para o compilador TypeScript por padrão. Typed EventEmitter cobre a restrição de ambas as pontas - nomes e formas de carga útil - para que esses erros apareçam em tempo de compilação em vez de simplesmente não fazer nada em tempo de execução.
"emit() é assíncrono, como a maioria das outras I/Os do Node." É totalmente síncrono - cada listener é executado, na ordem de registro, na pilha de chamadas atual, antes de emit() retornar; nada sobre o EventEmitter em si adia a execução para um tick posterior.
"EventEmitter é basicamente uma fila de mensagens leve." Uma fila armazena e persiste mensagens para entrega posterior ou repetida; EventEmitter não tem buffer algum - se nada estiver ouvindo quando emit() é executado, essa emissão simplesmente desaparece.
"Um erro lançado dentro de um listener é capturado pelo emitter." Ele se propaga exatamente como qualquer throw síncrono, potencialmente interrompendo listeners posteriores para a mesma emissão e aparecendo no local da chamada emit() - o emitter não fornece try/catch implícito.
"maxListeners é um limite rígido que começará a descartar listeners." Ele apenas dispara um aviso (MaxListenersExceededWarning) quando excedido - é uma heurística de detecção de vazamento, não um limite imposto, e aumentá-lo às vezes é a correção correta em vez de sempre um sinal de alerta.
"Apenas código de aplicação personalizado usa EventEmitter - as APIs principais do Node têm sua própria coisa." O oposto é verdadeiro: streams, servidores HTTP, sockets e processos filhos são todos EventEmitters por baixo de seus nomes de método específicos, usando exatamente este mecanismo.
Um mecanismo de publish/subscribe intraprocessual onde um objeto mantém eventos nomeados, e chamar emit(name, ...) executa síncronamente todos os listeners atualmente registrados para esse nome.
Por que tantas APIs principais do Node estendem EventEmitter?
Ele dá a qualquer objeto uma maneira uniforme de expor múltiplos sinais independentemente subscritíveis de uma única instância - os eventos 'data', 'error' e 'end' de um stream usam o mesmo mecanismo em vez de precisar de APIs de callback específicas e separadas.
O `emit()` é síncrono ou assíncrono?
Síncrono - ele chama todos os listeners registrados para esse nome de evento, em ordem, na pilha de chamadas atual, e só retorna depois que todos eles terminaram de executar.
O que acontece se um listener lançar uma exceção?
A exceção se propaga para fora da chamada emit() como qualquer throw síncrono normal - ela não é capturada pelo emitter, e pode impedir que qualquer listener registrado após o que lançou a exceção (para a mesma emissão) seja executado.
Por que um evento `'error'` não tratado trava o processo?
O Node trata 'error' de forma especial: se um emitter o emite e nenhum listener está registrado para esse evento específico, o Node o lança como uma exceção não capturada em vez de descartá-lo silenciosamente - uma escolha deliberada para tornar erros ignorados barulhentos em vez de invisíveis.
Um listener registrado após `emit()` ser executado recebe esse evento?
Não - EventEmitter não tem buffer ou replay; uma emissão que ocorre antes que um listener seja registrado simplesmente desaparece quando esse listener se inscreve.
Qual a diferença entre `on()` e `once()`?
on() registra um listener que é executado toda vez que o evento dispara; once() registra um que é executado apenas na próxima ocorrência, e então se remove automaticamente - útil para sinais únicos como "conexão estabelecida".
EventEmitter é um substituto para uma fila de mensagens?
Não - é inteiramente intraprocessual e em memória, sem persistência ou garantia de entrega, enquanto uma fila (Kafka, SQS, RabbitMQ) é construída especificamente para durabilidade e para cruzar fronteiras de processos ou serviços; eles resolvem problemas diferentes, mesmo que ambos sejam coloquialmente "eventos".
Por que o Node avisa sobre mais de 10 listeners em um evento?
O aviso padrão maxListeners é uma heurística de detecção de vazamento, não um limite rígido - acumular muitos listeners em um nome de evento é um sintoma comum de chamadas on() esquecidas sem remoção correspondente, então o Node o sinaliza cedo em vez de deixá-lo crescer silenciosamente.
Dois listeners diferentes para o mesmo evento podem rodar em paralelo?
Não - como emit() é síncrono, os listeners para uma emissão sempre rodam um após o outro na mesma pilha de chamadas, nunca concorrentemente; execução "paralela" exigiria que cada listener se transferisse para algo assíncrono internamente.
EventEmitter garante a ordem de execução dos listeners?
Sim, para um determinado nome de evento, os listeners rodam na ordem em que foram registrados - emit() percorre sua lista interna de listeners para esse nome sequencialmente, não em alguma sequência arbitrária ou reordenada.