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étodo | Faz |
|---|---|
on(name, fn), addListener | Chama 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), removeListener | Remove 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.