EventEmitter to klasa z modułu events w Node, która pozwala obiektowi ogłaszać nazwane zdarzenia. Listener rejestrujesz przez emitter.on('name', fn), a wywołujesz go przez emitter.emit('name', ...args); każdy listener dla tej nazwy się wykonuje, w kolejności dodania, z argumentami, które przekazałeś. Serwery HTTP, streamy i process to EventEmittery, więc te same kilka metod działa w całym Node.
Oba listenery wykonują się między before emit a after emit: emit wywołuje je synchronicznie i wraca dopiero, gdy skończą. Zamień kolejność dwóch wywołań shop.on, a dwie środkowe linie też zamienią się miejscami.
on, once i off
on trzyma listener, dopóki go nie usuniesz, once usuwa go po pierwszym wywołaniu, a off (to samo co removeListener) usuwa jeden. Żeby usunąć listener, potrzebujesz tej samej funkcji, którą dodałeś, więc nadaj jej nazwę zamiast przekazywać arrow function w miejscu.
first open ever wypisuje się raz, po Adzie. Gdy logOpen zostanie usunięty, nikt już nie nasłuchuje, więc ostatni emit zwraca false; door.removeAllListeners('open') usunąłby oba listenery jednym wywołaniem.
| Metoda | Co robi |
|---|---|
on(name, fn), addListener | Wywołuje fn przy każdym emit |
once(name, fn) | Wywołuje fn tylko przy następnym emit |
prependListener(name, fn) | Jak on, ale wykonuje się przed istniejącymi listenerami |
off(name, fn), removeListener | Usuwa tę funkcję |
emit(name, ...args) | Wywołuje listenery, zwraca true, jeśli jakieś były |
listenerCount(name) | Ile listenerów ma zdarzenie |
Zdarzenie error
'error' jest wyjątkowe. Jeśli je wyemitujesz i nikt nie nasłuchuje, emit rzuca błąd, a w prawdziwym programie nieprzechwycony wyjątek wywraca proces. Zawsze dodawaj listener 'error' do emiterów, które mogą zawieść, np. streamów i serwerów.
Bez listenera emit rzuca wyjątek i łapie go catch; po dodaniu listenera ten sam emit po prostu go wywołuje, a program działa dalej. Każda inna nazwa zdarzenia bez listenerów jest po cichu ignorowana.
Dziedziczenie po EventEmitter we własnej klasie
Zwykle twoja klasa rozszerza EventEmitter, wywołuje super() w konstruktorze i this.emit, gdy coś się dzieje. Wywołujący subskrybują wtedy twój obiekt jak każdy obiekt Node.
Wypisuje 25%, 50%, 75%, 100%, a potem photo.png uploaded. Przenieś up.start() nad dwie linie up.on i nic się nie wypisze: zdarzenia lecą, zanim ktokolwiek nasłuchuje.
Ta sama pułapka dotyczy emitowania w konstruktorze. Biblioteki, które muszą wyemitować zdarzenie od razu, opakowują wywołanie w process.nextTick, które wykonuje się po liniach on wywołującego (zobacz stronę o event loop).
Czekanie na zdarzenie z events.once
events.once(emitter, name) zwraca promise, który przy następnym wyemitowaniu rozwiązuje się z argumentami zdarzenia w postaci tablicy. Dzięki temu jednorazowe zdarzenie staje się czymś, na co możesz poczekać przez await, a promise zostaje odrzucony, jeśli emiter wcześniej wyemituje 'error'.
await czeka, aż timer wyemituje ready, a potem tablica rozkłada się na dwa argumenty. Dla zdarzenia, które pojawia się wiele razy, events.on(emitter, name) daje async iterator, po którym przejdziesz pętlą for await.
Częste błędy
Dodawanie listenera w funkcji, która wykonuje się często. Wywoływanie on w każdym handlerze żądania mnoży listenery i zużycie pamięci. Dodaj go raz albo użyj once.
Usuwanie funkcji zapisanej w miejscu. off('data', () => ...) nic nie usuwa, bo ta arrow function jest nowa. Trzymaj referencję do funkcji, którą dodałeś.
Oczekiwanie, że listenery wykonają się później. emit jest synchroniczny, więc wolny listener wstrzymuje kod, który wyemitował zdarzenie. Oznaczenie listenera jako async nie pomaga, bo jego kod i tak działa w tym samym wątku aż do pierwszego await. Podziel ciężką pracę na kawałki z setImmediate pomiędzy nimi albo przenieś ją do wątku roboczego (worker thread).
Najczęściej zadawane pytania
Czy EventEmitter jest asynchroniczny?
Nie. emit od razu wywołuje każdy listener, jeden po drugim, i wraca, gdy ostatni skończy. Kod w listenerze może uruchomić pracę asynchroniczną, ale sam listener jest wywoływany synchronicznie.
Co oznacza MaxListenersExceededWarning?
Node ostrzega, gdy do jednego zdarzenia dodasz więcej niż 10 listenerów, bo to często znaczy, że listener jest dodawany przy każdym żądaniu i nigdy nie jest usuwany. Usuń nadmiarowe listenery albo wywołaj emitter.setMaxListeners(n), jeśli naprawdę potrzebujesz więcej.
Co zwraca emit?
true, jeśli zdarzenie miało co najmniej jeden listener, false, jeśli nikt nie nasłuchiwał. Nie zwraca tego, co zwracają listenery.
Które obiekty Node.js są EventEmitterami?
Większość obiektów z rdzenia Node: serwery HTTP, żądania i odpowiedzi, streamy, sockety, process i interfejsy readline. Dlatego wszystkie mają on, once i off.