Гид по технологиям

Event emitters в Node.js

6 min read Node.js Обновлено 05 Jan 2026
Event emitters в Node.js — руководство
Event emitters в Node.js — руководство

Человек, держащий наклейку Node.js

Что такое EventEmitter

EventEmitter — это класс из встроенного модуля events, который позволяет компонентам приложения отправлять уведомления (события) и подписчикам реагировать на них. Краткое определение: объект-издатель уведомляет всех подписчиков о случившемся действии.

Определение термина: Event — именованное уведомление; Listener — функция, выполняемая при наступлении события.

Важно: EventEmitter вызывает слушатели синхронно в порядке регистрации, если не использовать специальные методы как prependListener.

Быстрый пример и инициализация

Перед использованием нужно импортировать класс и создать экземпляр:

const EventEmitter = require("events");

// Создаём экземпляр EventEmitter
const myEmitter = new EventEmitter();

Чтобы отправить событие, вызовите метод emit с именем события и необязательными аргументами:

myEmitter.emit("TestEvent", "foo", "bar", 1, 2);

При вызове emit все слушатели для этого события вызовутся синхронно в порядке регистрации. Метод вернёт true, если у события были слушатели, и false, если слушателей не было.

Прослушивание событий

Для подписки используйте метод on, который принимает имя события и callback-функцию. Метод возвращает ссылку на EventEmitter, поэтому можно делать цепочки.

// Первый слушатель
myEmitter.on("TestEvent", () => {
  console.log("TestEvent Emitted!!!");
});

// Второй слушатель — принимает аргументы
myEmitter.on("TestEvent", (...args) => {
  args = args.join(", ");
  console.log(`Event emitted with the following arguments: ${args}`);
});

myEmitter.emit("TestEvent", "foo", "bar", 1, 2);

Вывод в консоль будет в том порядке, в котором были зарегистрированы слушатели (сначала первый, затем второй).

Изменение порядка выполнения слушателей

Если нужно, чтобы слушатель выполнялся раньше других независимо от порядка регистрации, используйте prependListener:

myEmitter.on("TestEvent", () => {
  console.log("TestEvent Emitted!!!");
});

myEmitter.prependListener("TestEvent", () => {
  console.log("Executes first")
});

myEmitter.emit("TestEvent", "foo", "bar", 1, 2);

Здесь сначала выполнится “Executes first”, затем остальные слушатели. Если регистрировать несколько prependListener, они выполняются в порядке от последнего зарегистрированного к первому (LIFO среди prepend).

Подписка только один раз

Если слушатель должен сработать только при первой эмиссии, используйте once. Он автоматически удалит слушатель после первого вызова.

myEmitter.once("SingleEvent", () => {
  console.log("Event handled once");
});

myEmitter.emit("SingleEvent"); // Event handled once
myEmitter.emit("SingleEvent"); // Игнорируется

Если нужен приоритет для одноразового слушателя, применяйте prependOnceListener.

Обработка ошибок

Особое событие — error. Если EventEmitter эмитирует событие “error” и нет ни одного слушателя для него, Node.js завершит процесс с ошибкой. Поэтому важно явно обрабатывать ошибки через слушателя error.

myEmitter.on("error", (err) => {
  console.error(`Error: ${err.message || err}`);
});

myEmitter.emit("error", new Error("This is an error"));

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

Управление слушателями

EventEmitter предоставляет методы для получения и управления слушателями. Их нужно вызывать на экземпляре EventEmitter.

МетодАргументыЧто возвращает / делает
listenerCounteventNameЧисло слушателей для события
listenerseventNameМассив функций-слушателей
removeListenereventName, listenerУдаляет конкретный слушатель (нужно передать ту же функцию)
removeAllListenerseventName?Удаляет все слушатели для события или для всего эмиттера, если имя не указано
setMaxListenersnumberУстанавливает максимум слушателей на событие (по умолчанию 10). 0 или Infinity — безлимит

Примеры:

function onTest() {
  console.log('listener');
}

myEmitter.on('TestEvent', onTest);
console.log(myEmitter.listenerCount('TestEvent')); // количество слушателей
myEmitter.removeListener('TestEvent', onTest); // удаляет указанную функцию
myEmitter.removeAllListeners('TestEvent'); // удаляет всех слушателей TestEvent

myEmitter.setMaxListeners(20); // увеличиваем лимит слушателей

Обратите внимание: removeListener требует точно ту же ссылку на функцию, которая была передана в on/once.

Практические шаблоны использования

  • Локальные события внутри модуля: использовать EventEmitter, чтобы отделить логику и реакции на события.
  • Координация асинхронных шагов: эметируйте события на завершение операций, чтобы запускать следующую логику.
  • Инструменты и тесты: эмитируйте события в тестах для имитации внешних сигналов.

Пример: объединение результатов нескольких асинхронных операций через события

const results = [];
myEmitter.on('part', (data) => {
  results.push(data);
  if (results.length === 3) {
    myEmitter.emit('complete', results);
  }
});

Когда EventEmitter не подходит

  • Нужна отказоустойчивая очередь сообщений между процессами — используйте брокер сообщений (RabbitMQ, Kafka).
  • Нужна реактивная обработка с композициями потоков и трансформациями — рассмотрите RxJS/Observables.
  • Для одноразовых асинхронных операций с ожидаемым результатом удобнее промисы/async-await.

Ментальная модель и эвристики

  • Ментальная модель: publish–subscribe/observer. Компонент публикует событие — остальные подписчики получают уведомление.
  • Эвристики:
    • Подписчики должны быть зарегистрированы до того, как событие может быть эмитировано, если требуется среагировать.
    • Используйте once для одноразовых подписок, чтобы избежать утечек памяти.
    • Контролируйте количество слушателей через setMaxListeners, чтобы находить потенциальные утечки.

Безопасность и надёжность

  • Валидируйте входные данные событий — не доверяйте внешним строкам/объектам без проверки.
  • Не исполняйте динамически сформированный код внутри слушателей.
  • Ограничивайте влияние ошибок: ловите исключения внутри слушателей и по возможности эмитируйте события error с описательными сообщениями.
  • Для критичных систем используйте внешние механизмы очередей и журналирование ошибок.

Диагностика и отладка утечек слушателей

  • Ошибка “(node:…) MaxListenersExceededWarning” означает, что количество слушателей превысило значение по умолчанию (10).
  • Используйте listenerCount/listeners для поиска места, где накапливаются подписки.
  • В тестах после кейса вызывайте removeAllListeners или сохраняйте ссылку на созданные слушатели и удаляйте их.

Факт-бокс

  • EventEmitter вызывает слушатели синхронно в порядке регистрации.
  • По умолчанию максимум слушателей на событие — 10; можно изменить через setMaxListeners.
  • once автоматически удаляет слушатель после первого вызова.
  • Если событие “error” эмитируется без слушателей, процесс завершится с ошибкой.

Чек-лист для ролей

Разработчик:

  • Зарегистрировал слушатели до возможной эмиссии событий.
  • Обработал событие error.
  • Использовал once для одноразовых слушателей.
  • Удалил слушатели после завершения работы (если нужно).

Code reviewer:

  • Проверил, что removeListener получает ту же функцию, что и on.
  • Проверил на предмет возможных утечек слушателей.
  • Убедился, что данные события валидируются.

Ops/DevOps:

  • Настроил мониторинг предупреждений о превышении MaxListeners.
  • Логирует критичные события error для последующего анализа.

Мини-методология внедрения событий в модуль

  1. Определите события интерфейса модуля (пример: ready, data, error, close).
  2. Документируйте формат данных каждого события и обязательность полей.
  3. Реализуйте EventEmitter в модуле и экспортируйте экземпляр или класс.
  4. Покройте тестами сценарии: нормальный поток, многократные эмиссии, отсутствие слушателей, error.
  5. Внедрите проверку в code review: лимит слушателей, удаление, обработка error.

Примеры тест-кейсов и критерии приёмки

Критерии приёмки:

  • Событие вызывается при ожидаемом действии и слушатели получают корректные данные.
  • Одноразовый слушатель выполняется ровно один раз.
  • При эмиссии error приложение не падает, если есть слушатель error.
  • Нет утечек слушателей при многократном использовании модуля.

Тест-кейсы:

  1. Зарегистрировать два слушателя, вызвать событие, проверить порядок и аргументы.
  2. Зарегистрировать once, вызвать дважды, убедиться, что слушатель выполнился один раз.
  3. Эмитировать error без слушателя и с слушателем, проверить поведение процесса в тестовом окружении (мок/спец-настройка).
  4. Создать много подписчиков и ожидать предупреждения о MaxListeners, затем изменить лимит и проверить отсутствие предупреждения.

Сравнение с альтернативами

  • EventEmitter: локальные синхронные события, простой API, не рассчитан на межпроцессное взаимодействие.
  • Streams: для потоковых данных (I/O), поддерживают backpressure.
  • Promises/async-await: удобны для одиночного результата асинхронной операции.
  • Message brokers: надёжная доставка между сервисами, устойчивость к сбоям.

Риск-матрица и смягчение

  • Утечка слушателей — риск средней серьезности. Смягчение: setMaxListeners, removeAllListeners, четкая ответственность за удаление.
  • Аварийное завершение при error — высокий риск для непрерывных сервисов. Смягчение: всегда обрабатывать event “error” и логировать.
  • Неправильный порядок выполнения — низкий/средний. Смягчение: использовать prependListener при необходимости приоритета.

Советы по миграции и совместимости

  • При переносе кода на новую версию Node.js проверьте изменения в поведении событий в changelog Node.js.
  • Если архитектура становится распределённой, подумайте о переходе от EventEmitter к брокеру сообщений.

Заключение

EventEmitter — мощный и лёгкий инструмент для организации событийной архитектуры внутри Node.js приложения. Он хорошо подходит для локальной координации действий, но требует дисциплины: обработка ошибок, контроль числа слушателей и удаление ненужных подписок помогут избежать сбоев и утечек памяти.

Внедряйте события с чёткой спецификацией формата данных, покрывайте тестами ключевые сценарии и применяйте вышеприведённые шаблоны для надёжности.

Поделиться: X/Twitter Facebook LinkedIn Telegram
Автор
Редакция

Похожие материалы

Как увеличить память на Android TV
Android TV

Как увеличить память на Android TV

Как добавить вершину в Blender
Blender

Как добавить вершину в Blender

Самодельный датчик движения HomeKit на ESP8266
Умный дом

Самодельный датчик движения HomeKit на ESP8266

Как делать заметки по роману в OneNote
Образование

Как делать заметки по роману в OneNote

Что установить на новом ПК — безопасная установка ПО
Программы

Что установить на новом ПК — безопасная установка ПО

Настройки сборки Unity и запуск проекта
Game Development

Настройки сборки Unity и запуск проекта