Home > Blog > Desenvolvimento Web
Desenvolvimento Web
JavaScript
Programação

EventTarget no Node.js: Guia Prático

Atualizado em: 17 de agosto de 2026

Estação de trabalho usada no desenvolvimento de aplicações Node.js

O EventTarget no Node.js oferece um modelo de eventos compatível com a plataforma Web. Com addEventListener(), removeEventListener(), dispatchEvent(), Event e CustomEvent, bibliotecas podem compartilhar padrões entre navegador, backend e APIs como AbortSignal.

O Node.js também possui EventEmitter, uma API tradicional e muito usada. As duas soluções têm objetivos parecidos, mas diferenças importantes em tratamento de erros, retorno de listeners, quantidade de listeners, nomes de eventos e compatibilidade com padrões Web.

Neste guia, você aprenderá a criar um EventTarget, registrar listeners, usar once e signal, despachar eventos customizados, remover handlers, tratar erros, comparar com EventEmitter, implementar classes, testar e evitar vazamentos.

O que é EventTarget?

EventTarget é a interface base para objetos que recebem e despacham eventos. A documentação oficial de EventTarget no Node.js descreve o comportamento do runtime. A documentação de EventTarget na MDN apresenta o padrão Web.

Para o modelo tradicional, consulte EventEmitter no Node.js. Para cancelamento, veja AbortController no Node.js. O artigo sobre Event Loop no Node.js ajuda a entender a ordem de execução.

Criando um EventTarget

const target = new EventTarget();

O objeto pode receber listeners e despachar eventos.

Registrando um listener

function handleReady(event) {
  console.log('Aplicação pronta', event.type);
}

target.addEventListener('ready', handleReady);

Despachando um evento

const event = new Event('ready');
target.dispatchEvent(event);

O dispatch é síncrono: os listeners são executados durante a chamada, salvo quando eles próprios agendam trabalho assíncrono.

Removendo o listener

target.removeEventListener('ready', handleReady);

É necessário usar a mesma referência de função. Uma arrow function criada novamente não corresponde ao listener original.

Erro comum com função anônima

target.addEventListener('ready', () => {
  console.log('ready');
});

target.removeEventListener('ready', () => {
  console.log('ready');
});

O listener não é removido porque são duas funções diferentes.

Opção once

target.addEventListener(
  'connected',
  handleConnection,
  { once: true }
);

O handler é removido automaticamente depois da primeira execução.

Opção signal

const controller = new AbortController();

target.addEventListener(
  'message',
  handleMessage,
  { signal: controller.signal }
);

controller.abort();

Abortar remove o listener, facilitando cleanup de componentes.

Vários listeners com o mesmo sinal

const controller = new AbortController();

for (const eventName of ['open', 'message', 'close']) {
  target.addEventListener(
    eventName,
    handleEvent,
    { signal: controller.signal }
  );
}

controller.abort();

Um único abort remove todo o grupo associado.

CustomEvent

const event = new CustomEvent('user-created', {
  detail: {
    id: 42,
    name: 'Ana'
  }
});

target.dispatchEvent(event);

O payload fica em event.detail. A disponibilidade global de CustomEvent depende da versão do Node.js.

Recebendo detail

target.addEventListener('user-created', event => {
  console.log(event.detail.id);
});

Valide o conteúdo. Um evento interno ainda pode vir de uma versão incompatível do componente.

Classe que estende EventTarget

class JobQueue extends EventTarget {
  add(job) {
    this.dispatchEvent(new CustomEvent('job-added', {
      detail: { job }
    }));
  }
}

const queue = new JobQueue();

Encapsulando eventos

Evite permitir que qualquer parte da aplicação dispare qualquer evento. Exponha métodos que validam o estado e depois chamam dispatchEvent().

dispatchEvent() e retorno

O método retorna um booleano relacionado a cancelamento do evento. Não use esse retorno como coleção de resultados dos listeners.

Eventos canceláveis

const event = new Event('before-save', {
  cancelable: true
});

const allowed = target.dispatchEvent(event);

Um listener pode chamar preventDefault(). Se isso ocorrer em um evento cancelável, o retorno indica que a ação padrão foi cancelada.

preventDefault()

target.addEventListener('before-save', event => {
  if (!isValid()) {
    event.preventDefault();
  }
});

Esse padrão pode representar uma etapa de validação, mas uma função explícita costuma ser mais clara para regras de negócio críticas.

defaultPrevented

target.addEventListener('before-save', event => {
  console.log(event.defaultPrevented);
});

Captura e bubbling

No DOM, eventos podem atravessar árvores de elementos. Um EventTarget simples no Node.js não possui automaticamente uma árvore DOM. Opções de capture podem não ter o mesmo efeito prático esperado em objetos isolados.

stopPropagation()

Sem uma hierarquia de alvos, propagação possui utilidade limitada. Não projete uma árvore complexa de eventos apenas para imitar o DOM no backend.

Listener como objeto

const listener = {
  handleEvent(event) {
    console.log(event.type);
  }
};

target.addEventListener('ready', listener);

A interface aceita objetos com handleEvent() em implementações compatíveis.

EventTarget versus EventEmitter

  • EventTarget: padrão Web, usa Event, addEventListener e dispatchEvent.
  • EventEmitter: API Node.js, usa on, once e emit.

Payload

EventEmitter permite vários argumentos:

emitter.emit('data', id, value);

EventTarget normalmente transporta dados dentro do objeto Event, especialmente em detail.

Evento error

EventEmitter possui comportamento especial para o evento error. EventTarget não deve ser tratado como se tivesse exatamente a mesma regra.

Consulte Erros no Node.js quando disponível e a documentação da versão usada.

Listeners assíncronos

target.addEventListener('data', async event => {
  await save(event.detail);
});

dispatchEvent() não aguarda a Promise retornada. Uma rejeição precisa ser tratada dentro do listener.

Tratando erro assíncrono

target.addEventListener('data', event => {
  Promise.resolve(handleData(event.detail))
    .catch(error => {
      logger.error('event_handler_failed', {
        message: error.message
      });
    });
});

Quando precisa aguardar todos?

Se a operação só pode continuar depois de todos os handlers, EventTarget pode não ser a abstração ideal. Use uma lista explícita de funções e Promise.all() ou um pipeline.

Ordem dos listeners

Listeners são chamados na ordem definida pela implementação e registro, mas regras de negócio não devem depender de detalhes frágeis quando os componentes são independentes.

Remoção durante dispatch

Adicionar ou remover handlers enquanto um evento está sendo processado pode ter comportamento específico. Teste se sua arquitetura depende disso.

Duplicidade de listeners

Registrar a mesma função com o mesmo tipo e opções pode ser tratado como duplicado pelo padrão. Não dependa disso para gerenciar ciclo de vida; controle a inscrição explicitamente.

Vazamento de listeners

Um listener mantém referências ao escopo capturado. Se o target vive por toda a aplicação, objetos grandes podem permanecer acessíveis.

Cleanup com AbortController

Associar listeners a um sinal é uma das formas mais seguras de cleanup:

class Component {
  #controller = new AbortController();

  start(target) {
    target.addEventListener(
      'update',
      this.#handleUpdate,
      { signal: this.#controller.signal }
    );
  }

  stop() {
    this.#controller.abort();
  }

  #handleUpdate = event => {};
}

Max listeners

EventEmitter possui avisos tradicionais de limite. APIs do módulo events também podem ajudar a consultar ou configurar limites para EventTarget em versões modernas. Não use um número alto para esconder vazamento.

getEventListeners()

O módulo node:events pode oferecer getEventListeners() para diagnóstico:

const {
  getEventListeners
} = require('node:events');

console.log(
  getEventListeners(target, 'message').length
);

Use em testes e diagnóstico, não como lógica central.

setMaxListeners()

Versões compatíveis permitem aplicar limite a EventTarget:

const {
  setMaxListeners
} = require('node:events');

setMaxListeners(20, target);

AbortSignal e listeners

AbortSignal é um EventTarget. É possível escutar o evento abort:

signal.addEventListener('abort', () => {
  cleanup();
}, { once: true });

Motivo do cancelamento

signal.addEventListener('abort', () => {
  console.log(signal.reason);
});

Veja AbortController no Node.js.

WebSocket

A API WebSocket compatível com a Web usa eventos como open, message, error e close. Entender EventTarget facilita trabalhar com o cliente nativo.

Fetch e streams

Várias APIs Web presentes no Node.js seguem EventTarget ou AbortSignal, tornando o padrão útil para bibliotecas interoperáveis.

Tipagem com TypeScript

TypeScript pode mapear nomes para tipos de eventos:

interface AppEventMap {
  ready: Event;
  update: CustomEvent<{ id: number }>;
}

Uma classe wrapper pode oferecer métodos tipados sem alterar a API nativa.

Wrapper de inscrição

function on(target, type, listener, signal) {
  target.addEventListener(type, listener, { signal });
}

Centralizar pode padronizar logging e cleanup, mas evite esconder demais o comportamento.

Eventos de domínio

Para eventos de negócio distribuídos, EventTarget não oferece persistência, replay ou entrega entre processos. Use broker ou outbox quando a mensagem precisa sobreviver.

Eventos internos

EventTarget funciona bem para:

  • ciclo de vida de componente;
  • notificação local;
  • adapters Web;
  • cancelamento;
  • bibliotecas multiplataforma;
  • estado de conexão.

Não use como fila

Eventos são entregues apenas aos listeners presentes durante o dispatch. Não existe armazenamento para consumidores futuros.

Backpressure

EventTarget não possui backpressure. Um produtor rápido pode disparar trabalho assíncrono em excesso.

Para dados contínuos, use Streams no Node.js ou filas limitadas.

Coalescimento

Eventos de atualização muito frequentes podem ser agrupados:

let pending = false;

function notifyUpdate() {
  if (pending) return;
  pending = true;

  queueMicrotask(() => {
    pending = false;
    target.dispatchEvent(new Event('update'));
  });
}

Observabilidade

Registre eventos importantes, duração de handlers e falhas. Não registre detail completo quando contém dados pessoais.

Testando eventos

const test = require('node:test');
const assert = require('node:assert/strict');

test('despacha evento', () => {
  const target = new EventTarget();
  let received = false;

  target.addEventListener('ready', () => {
    received = true;
  });

  target.dispatchEvent(new Event('ready'));
  assert.equal(received, true);
});

Testando once

let count = 0;

target.addEventListener('tick', () => {
  count += 1;
}, { once: true });

target.dispatchEvent(new Event('tick'));
target.dispatchEvent(new Event('tick'));

assert.equal(count, 1);

Testando cancelamento

Crie evento cancelável, chame preventDefault e verifique o retorno de dispatch.

Testando cleanup

Use AbortController, aborte e confirme que o listener não é mais executado.

Erros comuns

  • Remover outra função: o listener permanece.
  • Esperar Promises: dispatchEvent não aguarda handlers async.
  • Usar como fila: eventos antigos são perdidos.
  • Não limpar listeners: referências vazam.
  • Copiar regras de EventEmitter: comportamento de error difere.
  • Enviar payload gigante: handlers recebem dados desnecessários.
  • Sem backpressure: trabalho assíncrono acumula.

Boas práticas

  • Guarde referências de handlers.
  • Use once quando apropriado.
  • Associe AbortSignal.
  • Use CustomEvent para payload.
  • Valide detail.
  • Trate erros dentro de handlers async.
  • Não dependa de replay.
  • Evite payloads grandes.
  • Use streams para fluxo contínuo.
  • Teste cleanup.

Conclusão

O EventTarget no Node.js fornece uma API de eventos compatível com a plataforma Web e integra naturalmente com AbortSignal, WebSocket e outras interfaces modernas.

Ele é uma boa escolha para bibliotecas interoperáveis e notificações locais. Para usá-lo com segurança, mantenha handlers removíveis, associe sinais de cleanup, trate Promises dentro dos listeners e não confunda eventos com filas persistentes. Quando é necessário aguardar resultados ou controlar backpressure, uma abstração explícita costuma ser melhor.

Os 10 Melhores Cursos de Programação de 2026

Descubra os melhores cursos de programação. Aprenda a escolher o curso ideal para iniciar ou avançar na carreira de desenvolvedor

POSTS RELACIONADOS

Ver todos

Seta para a direita