Menu

EventEmitter в Node.js: on, emit, once и свои события

EventEmitter из модуля events в Node позволяет объекту сообщать об именованных событиях через emit, а другому коду реагировать на них через on или once. Слушатели выполняются синхронно, в том порядке, в котором их добавили.

На этой странице есть исполняемые редакторы: меняйте, запускайте и сразу видите результат.

EventEmitter это класс из модуля events в Node, который позволяет объекту сообщать об именованных событиях. Слушателя регистрируют через emitter.on('name', fn), а вызывают через emitter.emit('name', ...args); выполняются все слушатели с этим именем, в порядке добавления, с переданными вами аргументами. HTTP-серверы, потоки и process все являются EventEmitter, поэтому одни и те же несколько методов работают в Node везде.

Оба слушателя выполняются между before emit и after emit: emit вызывает их синхронно и возвращается, только когда они закончат. Поменяйте местами два вызова shop.on, и две средние строки тоже поменяются.

on, once и off

on держит слушателя, пока вы его не удалите, once удаляет его после первого вызова, а off (то же самое, что removeListener) удаляет одного слушателя. Чтобы удалить слушателя, нужна та же самая функция, которую вы добавили, поэтому дайте ей имя, а не передавайте стрелочную функцию прямо в вызов.

first open ever выводится один раз, после Ada. Когда logOpen удалён, никто не слушает, поэтому последний emit возвращает false; door.removeAllListeners('open') убрал бы обоих слушателей одним вызовом.

МетодЧто делает
on(name, fn), addListenerВызывает fn при каждом emit
once(name, fn)Вызывает fn только при следующем emit
prependListener(name, fn)Как on, но выполняется раньше уже добавленных слушателей
off(name, fn), removeListenerУдаляет эту функцию
emit(name, ...args)Вызывает слушателей, возвращает true, если они были
listenerCount(name)Сколько слушателей у события

Событие error

'error' особенное. Если вы его отправите, а никто не слушает, emit выбросит ошибку, а в реальной программе неперехваченное исключение роняет процесс. Всегда добавляйте слушателя 'error' к emitter'ам, которые могут сломаться, например к потокам и серверам.

Без слушателя emit выбрасывает ошибку, и её ловит catch; когда слушатель добавлен, тот же emit просто вызывает его, и программа продолжает работу. Любое другое событие без слушателей молча игнорируется.

Наследование от EventEmitter в своём классе

Обычно класс наследуют от EventEmitter, вызывают super() в конструкторе и this.emit, когда что-то происходит. Тогда вызывающий код подписывается на ваш объект так же, как на любой объект Node.

Программа выводит 25%, 50%, 75%, 100% и затем photo.png uploaded. Перенесите up.start() выше двух строк с up.on, и ничего не выведется: события срабатывают раньше, чем кто-то начал слушать.

Та же ловушка возникает, если отправлять события из конструктора. Библиотеки, которым нужно отправить событие сразу, оборачивают вызов в process.nextTick, и он выполняется после строк с on у вызывающего кода (см. страницу про event loop).

Ждём событие через events.once

events.once(emitter, name) возвращает промис, который при следующей отправке события разрешается его аргументами в виде массива. Так одноразовое событие превращается в то, что можно ждать через await, а если emitter раньше отправит 'error', промис будет отклонён.

await ждёт, пока таймер отправит ready, а затем массив деструктурируется в два аргумента. Для события, которое срабатывает много раз, events.on(emitter, name) даёт асинхронный итератор, по которому можно пройти циклом for await.

Частые ошибки

Добавление слушателя внутри функции, которая вызывается часто. Вызов on в каждом обработчике запроса копит слушателей и память. Добавьте слушателя один раз или используйте once.

Удаление стрелочной функции, переданной прямо в вызов. off('data', () => ...) ничего не удаляет, потому что эта стрелочная функция новая. Храните ссылку на функцию, которую добавили.

Ожидание, что слушатели выполнятся позже. emit синхронный, поэтому медленный слушатель задерживает код, который отправил событие. Пометка слушателя как async не помогает: его код всё равно выполняется в том же потоке до первого await. Разбейте тяжёлую работу на части с setImmediate между ними или вынесите её в рабочий поток (worker thread).

Часто задаваемые вопросы

EventEmitter асинхронный?

Нет. emit сразу вызывает каждого слушателя, одного за другим, и возвращается, когда закончит последний. Код внутри слушателя может запустить асинхронную работу, но сам слушатель вызывается синхронно.

Что значит MaxListenersExceededWarning?

Node предупреждает, когда на одно событие добавлено больше 10 слушателей, потому что часто это значит, что слушатель добавляется на каждом запросе и никогда не удаляется. Удалите лишних слушателей или вызовите emitter.setMaxListeners(n), если вам действительно нужно больше.

Что возвращает emit?

true, если у события был хотя бы один слушатель, и false, если никто не слушал. То, что возвращают слушатели, он не возвращает.

Какие объекты Node.js являются EventEmitter?

Большинство встроенных объектов Node: HTTP-серверы, запросы и ответы, потоки, сокеты, process и интерфейсы readline. Поэтому у всех них общие on, once и off.

Иллюстрация языков программирования Coddy

Учитесь программировать с Coddy

Начать