Menu

EventEmitter no Node.js: on, emit, once e eventos próprios

O EventEmitter do módulo events do Node permite que um objeto anuncie eventos com nome usando emit e que outro código reaja com on ou once. Os listeners rodam de forma síncrona, na ordem em que foram adicionados.

Esta página tem editores executáveis. Edite, execute e veja a saída na hora.

EventEmitter é a classe do módulo events do Node que permite a um objeto anunciar eventos com nome. Você registra um listener com emitter.on('name', fn) e dispara com emitter.emit('name', ...args); todo listener daquele nome roda, na ordem em que foi adicionado, com os argumentos que você passou. Servidores HTTP, streams e process são todos EventEmitters, então os mesmos poucos métodos funcionam em todo lugar no Node.

Os dois listeners rodam entre before emit e after emit: emit os chama de forma síncrona e só retorna quando eles terminam. Troque as duas chamadas de shop.on de lugar e as duas linhas do meio também trocam.

on, once e off

on mantém um listener até você removê-lo, once o remove depois da primeira chamada, e off (o mesmo que removeListener) remove um. Para remover um listener você precisa da mesma função que adicionou, então dê um nome a ela em vez de passar uma arrow function inline.

first open ever imprime uma vez, depois de Ada. Depois que logOpen é removido ninguém está escutando, então o último emit retorna false; door.removeAllListeners('open') teria limpado os dois listeners em uma chamada só.

MétodoFaz
on(name, fn), addListenerChama fn em todo emit
once(name, fn)Chama fn só no próximo emit
prependListener(name, fn)Como on, mas roda antes dos listeners existentes
off(name, fn), removeListenerRemove aquela função
emit(name, ...args)Chama os listeners, retorna true se havia algum
listenerCount(name)Quantos listeners o evento tem

O evento error

'error' é especial. Se você emite esse evento e ninguém escuta, emit lança o erro, e em um programa real um throw não tratado derruba o processo. Sempre adicione um listener de 'error' em emitters que podem falhar, como streams e servidores.

Sem listener, emit lança e o catch pega; com o listener adicionado, o mesmo emit só o chama e o programa segue. Qualquer outro nome de evento sem listeners é ignorado em silêncio.

Estenda EventEmitter na sua própria classe

O jeito comum de usar é fazer sua classe estender EventEmitter, chamar super() no construtor e this.emit quando algo acontece. Quem usa a classe então se inscreve no seu objeto como em qualquer objeto do Node.

Ele imprime 25%, 50%, 75%, 100% e depois photo.png uploaded. Mova up.start() para cima das duas linhas up.on e nada imprime: os eventos disparam antes de alguém escutar.

A mesma armadilha vale para emitir de dentro de um construtor. Bibliotecas que precisam emitir logo de cara envolvem a chamada em process.nextTick, que roda depois das linhas on de quem chamou (veja a página do event loop).

Espere um evento com events.once

events.once(emitter, name) retorna uma promise que resolve com os argumentos do evento, como array, na próxima vez que ele for emitido. Ela transforma um evento de uma vez só em algo em que você pode dar await, e rejeita se o emitter emitir 'error' antes.

O await pausa até o timer emitir ready, e então o array é desestruturado nos dois argumentos. Para um evento que dispara muitas vezes, events.on(emitter, name) dá um async iterator que você percorre com for await.

Erros comuns

Adicionar um listener dentro de uma função que roda com frequência. Chamar on em cada handler de requisição acumula listeners e memória. Adicione uma vez, ou use once.

Remover uma função inline. off('data', () => ...) não remove nada, porque essa arrow function é uma nova. Guarde uma referência à função que você adicionou.

Esperar que os listeners rodem depois. emit é síncrono, então um listener lento segura o código que emitiu. Marcar o listener como async não ajuda, porque o código dele continua rodando na mesma thread até o primeiro await. Divida o trabalho pesado em partes com setImmediate entre elas, ou leve-o para uma worker thread.

Perguntas frequentes

O EventEmitter é assíncrono?

Não. emit chama cada listener na hora, um depois do outro, e retorna quando o último termina. O código dentro de um listener pode iniciar trabalho assíncrono, mas o listener em si é chamado de forma síncrona.

O que significa o aviso MaxListenersExceededWarning?

O Node avisa quando mais de 10 listeners são adicionados para um evento, porque isso muitas vezes significa que um listener é adicionado a cada requisição e nunca removido. Remova os listeners extras, ou chame emitter.setMaxListeners(n) se você realmente precisa de mais.

O que o emit retorna?

true se o evento tinha pelo menos um listener, false se ninguém estava escutando. Ele não retorna o que os listeners retornam.

Quais objetos do Node.js são EventEmitters?

A maioria dos objetos centrais do Node: servidores HTTP, requisições e respostas, streams, sockets, process e interfaces readline. Por isso todos compartilham on, once e off.

Ilustração das linguagens de programação do Coddy

Aprenda a programar com o Coddy

Começar