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

EventEmitter в Node.js — как эмитить и слушать

7 min read Node.js Обновлено 16 Dec 2025
EventEmitter в Node.js — как эмитить и слушать
EventEmitter в Node.js — как эмитить и слушать

человек держит наклейку Node.js

Event emitters — это объекты в Node.js, которые сигнализируют о наступлении события путём отправки сообщения. Модель событий позволяет отделить генерацию событий от их обработки: один модуль эмитит событие, другой реагирует на него.

Определение в одну строку: EventEmitter — класс из встроенного модуля events, который обеспечивает регистрацию слушателей и синхронную передачу аргументов при эмиссии события.

Основные понятия

  • Эмитер (EventEmitter) — объект, который может эмитить события.
  • Событие (event) — именованный сигнал, например “data” или “error”.
  • Слушатель (listener) — функция, которая вызывается при эмиссии события.

Важно: слушатели должны быть зарегистрированы до того, как соответствующее событие будет эмитировано, иначе они не сработают.

Генерация событий

Для работы с EventEmitter не требуется установка сторонних пакетов — модуль events встроен в Node.js. Небольшой пример создания эмитера и эмисии события:

const EventEmitter = require('events');

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

// Эмитим событие с аргументами
myEmitter.emit('TestEvent', 'foo', 'bar', 1, 2);

Метод emit принимает имя события и произвольное количество аргументов. При вызове он синхронно последовательно вызывает всех зарегистрированных слушателей в порядке регистрации (если не применялись методы изменения порядка).

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

Стандартный метод регистрации слушателя — on. Он принимает имя события и функцию-обработчик и возвращает тот же экземпляр EventEmitter (что позволяет цепочкам вызовов).

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

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

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”, потом “TestEvent Emitted!!!”. Если вы зарегистрируете несколько prependListener, они выполнятся в порядке от последнего добавленного к первому (LIFO среди prepend).

Однократное прослушивание

Иногда слушатель должен сработать только один раз. Для этого существует метод once — он автоматически удалит слушатель после первого вызова.

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

myEmitter.emit('SingleEvent'); // Event handled once
myEmitter.emit('SingleEvent'); // проигнорировано

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

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

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

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

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

Без слушателя по имени “error” эмит события типа error приведёт к аварийному завершению процесса.

Важно: не используйте обработчик error для сокрытия критических ошибок — логируйте и при необходимости пробрасывайте их или корректно завершайте работу.

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

EventEmitter предоставляет методы управления подписками:

  • listenerCount(eventName) — количество слушателей для события;
  • listeners(eventName) — массив функций-слушателей;
  • removeListener(eventName, listener) — удаляет указанного слушателя;
  • removeAllListeners([eventName]) — удаляет всех слушателей (все события или для конкретного eventName);
  • setMaxListeners(n) — изменяет лимит слушателей на событие (по умолчанию 10).

Пример удаления слушателя:

function handler() {
  console.log('Handler');
}

myEmitter.on('TestEvent', handler);
myEmitter.removeListener('TestEvent', handler);

Функция removeListener требует точной ссылки на функцию-слушатель — анонимные функции удалить сложнее, поэтому при намерении потом отписаться предпочтительны именованные функции.

Таблица методов EventEmitter

МетодАргументыЧто делает
emiteventName, …argsЭмитит событие и синхронно вызывает слушателей
oneventName, listenerРегистрирует слушателя
onceeventName, listenerРегистрирует одноразовый слушатель
prependListenereventName, listenerДобавляет слушателя в начало очереди
prependOnceListenereventName, listenerОдноразовый слушатель с приоритетом
removeListenereventName, listenerУдаляет указанного слушателя
removeAllListeners[eventName]Удаляет всех слушателей (или для события)
listenerseventNameВозвращает массив слушателей
listenerCounteventNameВозвращает число слушателей
setMaxListenersnУстанавливает предел слушателей (0 или Infinity — без ограничения)

Факты: по умолчанию maxListeners = 10. Увеличивайте его только при обоснованной необходимости и контролируемом жизненном цикле слушателей.

Когда модель событий даёт преимущества и когда нет

Когда подходит EventEmitter:

  • Локальная синхронизация разных модулей внутри одного процесса;
  • Реакция на внутренние состояния, потоки данных, ивенты I/O;
  • Публикация/подписка в пределах приложения (не для межпроцессного общения).

Когда лучше искать альтернативы:

  • Требуется распределённая система и обмен событиями между процессами/серверами — используйте очередь сообщений (RabbitMQ, Redis Pub/Sub, Kafka);
  • Нужно управлять асинхронной последовательностью операций с ожидаемой возвратной информацией — рассмотрите Promise/async/await, async iterators или RxJS;
  • Нужна гарантия доставки или повторная доставка — используйте специализированные брокеры сообщений.

Альтернативы и сравнение

Краткая матрица выбора:

ЗадачаEventEmitterPromise/asyncMessage brokerRxJS
Локальные простые событияОтличноПлохоПлохоОК
Асинхронные цепочки с гарантиейСреднеОтличноХорошоОтлично
Горизонтальное масштабированиеПлохоПлохоОтличноСредне
Управление потоком (backpressure)ОграниченоСреднеОтличноОтлично

Выбор зависит от границ ответственности, требований к отказоустойчивости и масштабируемости.

Типичные проблемы и их решения

  • Утечки слушателей (listener leaks): если вы постоянно добавляете слушатели и не удаляете их, память растёт. Решения: removeListener/removeAllListeners, setMaxListeners, использовать once для одноразовых обработчиков.

  • Ошибки без обработчика error: регистрируйте общий обработчик ‘error’ или передавайте ошибки по цепочке вызовов через Promise.

  • Непредсказуемый порядок выполнения: используйте prependListener/append логически либо управляйте порядком через отдельные механизмы (через очереди).

Пример мониторинга количества слушателей:

const count = myEmitter.listenerCount('TestEvent');
console.log(`Listeners for TestEvent: ${count}`);

Безопасность и приватность

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

Тестирование и критерии приёмки

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

  • Событие эмитится и все зарегистрированные слушатели вызываются в ожидаемом порядке.
  • Одноразовые слушатели вызываются ровно один раз и затем удаляются.
  • При эмисии ошибки при отсутствии слушателя процесс не аварийно завершается (если это требование приложения).
  • Нет утечек слушателей при стресс-тестах (количество слушателей остаётся в допустимых пределах).

Тест-кейсы (минимум):

  1. Регистрируем два слушателя, эмитим событие — оба сработали в порядке регистрации.
  2. Регистрируем prependListener, эмитим — prepend сработал первым.
  3. Регистрируем once, эмитим несколько раз — слушатель вызван только один раз.
  4. Эмитим ‘error’ при наличии обработчика — ошибка логируется, процесс не падает.
  5. Добавляем и удаляем слушатель — listenerCount отражает изменения.

Мини‑методология внедрения EventEmitter в проект

  1. Определите границы: какие события должны быть локальными, а какие — глобальными.
  2. Создайте централизованные экземпляры (по модулю или по подсистеме) вместо разбросанных эмитеров.
  3. Документируйте имена событий и формат аргументов (контракт для слушателей).
  4. Внедрите мониторинг количества слушателей и предупреждения при росте.
  5. Добавьте тесты для ключевых сценариев (см. критерии приёмки).

Роли и чек‑листы

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

  • Использовать именованные функции для слушателей, если планируется отписка.
  • Не эмитить “error” без обработчика.
  • Использовать once для одноразовых задач.

Архитектор:

  • Определить, когда нужен брокер сообщений вместо EventEmitter.
  • Задокументировать контракты событий.

Операции (DevOps):

  • Настроить алерты на рост числа слушателей и частые ошибки ‘error’.
  • Обеспечить сбор логов и трассировки ошибок.

Примеры для реальных сценариев

  1. Координатор задач в памяти:
const tasks = new EventEmitter();

tasks.on('task:done', (id, result) => {
  console.log(`Task ${id} completed: ${result}`);
});

function completeTask(id, result) {
  tasks.emit('task:done', id, result);
}
  1. Поток данных — эмитим чанки и финализируем:
const streamEvents = new EventEmitter();

streamEvents.on('data', chunk => processChunk(chunk));
streamEvents.on('end', () => finishProcessing());

streamEvents.emit('data', Buffer.from('...'));
streamEvents.emit('end');

Модель принятия решения (Mermaid)

flowchart TD
  A[Нужно ли межпроцессное взаимодействие?] -->|Да| B[Использовать брокер сообщений]
  A -->|Нет| C[Локальные события]
  C --> D{Требуется порядок и гарантия доставки?}
  D -->|Да| B
  D -->|Нет| E[EventEmitter]

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

  • При переводе логики на распределённую архитектуру замените локальные emit на публикацию в брокер.
  • При миграции с ранних версий Node проверяйте доступность методов prependListener и prependOnceListener (они появились достаточно давно, но всегда полезно тестировать)
  • В браузерных сборках EventEmitter можно заменить на легковесные реализации (events-полифилы) или использовать встроенные DOM-события для компонентов.

Короткое резюме

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

Важно: документируйте контракт событий и тестируйте критические сценарии, чтобы избежать утечек и неожиданных завершений процесса.

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

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

Как найти приложения для Android Wear 2.0
Android.

Как найти приложения для Android Wear 2.0

Как размыть фон в Canva: 3 простых способа
Дизайн

Как размыть фон в Canva: 3 простых способа

Установить и протестировать Windows 10 S
Windows

Установить и протестировать Windows 10 S

RRoD на Xbox 360: как устранить и диагностировать
Техподдержка

RRoD на Xbox 360: как устранить и диагностировать

Как проверить версию Ubuntu
Linux

Как проверить версию Ubuntu

Как сделать копию документа Word
Работа с документами

Как сделать копию документа Word