Что такое EventEmitter в Node.js?

MiddleNode.js · Backend·Обновлено 3 июля 2026
Коротко
EventEmitter — это базовый класс из модуля events, реализующий паттерн «издатель-подписчик» (pub/sub): объект может генерировать именованные события, а другие части кода — подписываться на них и реагировать через callback-функции.

EventEmitter в Node.js

EventEmitter — ключевой строительный блок Node.js, находящийся в стандартном модуле events. Большинство встроенных объектов Node.js (http.Server, fs.ReadStream, net.Socket и другие) наследуются от EventEmitter.

Основные методы

  • emitter.on(event, listener) — подписывается на событие (псевдоним: addListener)
  • emitter.once(event, listener) — подписывается на событие только один раз, после срабатывания автоматически отписывается
  • emitter.off(event, listener) — отписывается от события (псевдоним: removeListener)
  • emitter.emit(event, ...args) — генерирует событие и вызывает всех подписчиков синхронно
  • emitter.removeAllListeners([event]) — удаляет всех подписчиков
  • emitter.listeners(event) — возвращает массив подписчиков
  • emitter.listenerCount(event) — количество подписчиков

Важные особенности

Синхронное выполнение. Метод emit вызывает всех слушателей синхронно, в том порядке, в котором они были добавлены. Это означает, что обработчики блокируют event loop до завершения.

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

Лимит слушателей. По умолчанию максимальное число слушателей на одно событие равно 10. При превышении Node.js выводит предупреждение (это защита от утечек памяти). Лимит можно изменить через emitter.setMaxListeners(n).

Наследование. Чтобы добавить EventEmitter-функциональность к своему классу, достаточно унаследоваться от него.

Применение в реальном коде

EventEmitter используется для слабой связанности компонентов: модуль-производитель ничего не знает о потребителях — он просто эмитит события. Это позволяет легко добавлять новых подписчиков без изменения кода источника.

import { EventEmitter } from 'events';

class OrderService extends EventEmitter {
  createOrder(id: string) {
    // Логика создания заказа...
    this.emit('orderCreated', { id, createdAt: new Date() });
  }
}

const orderService = new OrderService();

// Подписчики независимы друг от друга
orderService.on('orderCreated', (order) => {
  console.log('Отправляем email для заказа', order.id);
});

orderService.on('orderCreated', (order) => {
  console.log('Обновляем аналитику для заказа', order.id);
});

// Обработчик ошибок обязателен
orderService.on('error', (err) => {
  console.error('Ошибка в OrderService:', err);
});

orderService.createOrder('order-42');

EventEmitter vs. RxJS / потоки

Для простых событий EventEmitter вполне достаточен. Если нужны операторы трансформации, backpressure или цепочки асинхронных операций — стоит рассмотреть RxJS или Node.js Streams, которые сами построены поверх EventEmitter.

Что хочет услышать интервьюер

Понимание паттерна издатель-подписчик и роли EventEmitter как его реализации в Node.js

Знание ключевых методов: on, once, off, emit и их семантики

Осознание синхронного характера emit и влияния на event loop

Знание особого поведения события error и почему его обязательно нужно обрабатывать

Умение наследоваться от EventEmitter для создания собственных классов с событийной моделью

Пример: Создание собственного класса на основе EventEmitter

import { EventEmitter } from 'events';

interface PaymentEvents {
  success: (transactionId: string, amount: number) => void;
  failure: (reason: string) => void;
  error: (err: Error) => void;
}

// Типизированный EventEmitter через declaration merging
class PaymentGateway extends EventEmitter {
  on<K extends keyof PaymentEvents>(event: K, listener: PaymentEvents[K]): this {
    return super.on(event, listener as any);
  }

  emit<K extends keyof PaymentEvents>(
    event: K,
    ...args: Parameters<PaymentEvents[K]>
  ): boolean {
    return super.emit(event, ...args);
  }

  async charge(amount: number): Promise<void> {
    try {
      // Имитация запроса к платёжной системе
      const txId = `tx-${Date.now()}`;
      this.emit('success', txId, amount);
    } catch (err) {
      this.emit('failure', 'Платёж отклонён');
      this.emit('error', err as Error);
    }
  }
}

const gateway = new PaymentGateway();

// Подписка на успешный платёж (постоянная)
gateway.on('success', (txId, amount) => {
  console.log(`Платёж ${txId} на сумму ${amount} ₽ прошёл`);
});

// Подписка только на первый сбой
gateway.once('failure', (reason) => {
  console.warn('Первый сбой платежа:', reason);
});

// Обработчик ошибок обязателен — иначе process упадёт
gateway.on('error', (err) => {
  console.error('Необработанная ошибка шлюза:', err.message);
});

gateway.charge(1500);

Пример: Утечка памяти и правильная отписка

import { EventEmitter } from 'events';

const bus = new EventEmitter();

function setupHandler() {
  const handler = (data: string) => {
    console.log('Получено:', data);
  };

  bus.on('message', handler);

  // Возвращаем функцию отписки — иначе handler будет жить вечно
  return () => bus.off('message', handler);
}

const unsubscribe = setupHandler();

bus.emit('message', 'привет');

// Обязательно отписываемся, когда компонент/модуль больше не нужен
unsubscribe();

bus.emit('message', 'это сообщение уже не придёт');

Типичные ошибки

Считают, что emit работает асинхронно — на самом деле он вызывает слушателей синхронно

Забывают вешать обработчик события error, что приводит к аварийному завершению процесса при генерации ошибки

Не снимают подписку через off/removeListener, что вызывает утечки памяти в долгоживущих объектах

Игнорируют предупреждение о превышении лимита слушателей (MaxListenersExceededWarning) вместо того, чтобы разобраться в причине

Путают once и on: уверены, что on подписывается один раз, хотя on вызывается при каждом emit

Лучшие курсы по теме

изображение курса

Docker и Ansible

Антон Ларичев
AI-тренажерыAI-тренажеры
Гарантия
Бонусы
иконка звёздочки рейтинга4.7
3 999 ₽ 6 990 ₽
Подробнее
изображение курса

Node.js с нуля

Антон Ларичев
AI-тренажерыAI-тренажеры
Практика в студииПрактика в студии
Гарантия
Бонусы
иконка звёздочки рейтинга4.8
3 999 ₽ 6 990 ₽
Подробнее
изображение курса

Nest.js с нуля

Антон Ларичев
AI-тренажерыAI-тренажеры
Практика в студииПрактика в студии
Гарантия
Бонусы
иконка звёздочки рейтинга4.6
3 999 ₽ 6 990 ₽
Подробнее