events: EventEmitter
EventEmitter — базовый класс Node.js для реализации паттерна Observer (Pub/Sub): позволяет объектам генерировать именованные события и регистрировать слушателей (handlers) на эти события.
Зачем нужно
EventEmitter — основа асинхронного программирования в Node.js. Все ключевые объекты наследуют от него: Stream, http.Server, process, fs.ReadStream. Понимание EventEmitter необходимо для работы с потоками, отладки memory leak (слишком много слушателей) и построения event-driven архитектуры внутри Node.js-сервиса.
Где используется
- Все Stream-объекты (
'data','end','error') http.Server— события'request','close'process—'exit','uncaughtException','SIGTERM'- Кастомные event bus внутри приложения
- Worker Threads IPC
Основной контент
Базовый EventEmitter
const EventEmitter = require('events');
const emitter = new EventEmitter();
// Зарегистрировать обработчик
emitter.on('data', (payload) => {
console.log('Получено:', payload);
});
// Одноразовый обработчик
emitter.once('connect', () => {
console.log('Подключено (только один раз)');
});
// Сгенерировать событие
emitter.emit('data', { id: 1, message: 'Hello' });
emitter.emit('connect');
emitter.emit('connect'); // повторно — обработчик не вызовется
// Удалить обработчик
function handler(data) { console.log(data); }
emitter.on('event', handler);
emitter.off('event', handler); // удалить
emitter.removeAllListeners('event'); // удалить все
// Количество обработчиков
console.log(emitter.listenerCount('data')); // 1
Наследование от EventEmitter
const EventEmitter = require('events');
class OrderService extends EventEmitter {
constructor(db) {
super;
this.db = db;
}
async create(orderData) {
const order = await this.db.orders.create(orderData);
// Уведомить подписчиков
this.emit('order:created', order);
return order;
}
async cancel(orderId) {
await this.db.orders.update({ id: orderId, status: 'cancelled' });
this.emit('order:cancelled', { orderId });
}
}
// Использование
const orderService = new OrderService(db);
// Подписаться на события
orderService.on('order:created', async (order) => {
await emailService.sendConfirmation(order);
});
orderService.on('order:created', async (order) => {
await inventoryService.reserve(order.items);
});
orderService.on('order:cancelled', async ({ orderId }) => {
await inventoryService.release(orderId);
});
Event Bus — глобальная шина событий
// events/bus.js
const EventEmitter = require('events');
class EventBus extends EventEmitter {
constructor {
super;
this.setMaxListeners(50); // поднять лимит
}
}
module.exports = new EventBus(); // singleton
// Публикатор
const bus = require('./events/bus');
bus.emit('payment:completed', { orderId: 42, amount: 999 });
// Подписчик (в другом модуле)
const bus = require('./events/bus');
bus.on('payment:completed', async ({ orderId }) => {
await OrderService.markAsPaid(orderId);
});
Async обработчики и ошибки
// EventEmitter не поддерживает async из коробки
// Ошибки в async-обработчиках не перехватываются
// НЕПРАВИЛЬНО:
emitter.on('data', async (data) => {
throw new Error('Это не поймает emitter'); // unhandledRejection!
});
// ПРАВИЛЬНО — оборачивать в try/catch:
emitter.on('data', async (data) => {
try {
await processData(data);
} catch (err) {
emitter.emit('error', err); // перенаправить в error handler
}
});
// Обязательный error handler:
emitter.on('error', (err) => {
console.error('Emitter error:', err);
// без этого Node.js упадёт с необработанным исключением!
});
Полезные методы
// Слушать один раз
emitter.once('ready', () => console.log('ready!'));
// Получить список событий
console.log(emitter.eventNames); // ['data', 'error']
// Получить обработчики
const handlers = emitter.listeners('data');
// Максимум слушателей (по умолчанию 10)
// При превышении: MaxListenersExceededWarning (не ошибка)
emitter.setMaxListeners(20);
Частые ошибки
- Нет обработчика
'error'— если объект emits'error'и нет listener, Node.js бросает исключение и падает; всегда добавлятьemitter.on('error', handler) - Memory Leak — добавлять обработчики в цикле или при каждом запросе без
removeListener; превышениеmaxListeners— сигнал утечки - Async-обработчики без try/catch — rejection не перехватывается EventEmitter
- Забыть вызвать
removeAllListeners— при уничтожении объекта оставшиеся listeners держат ссылку, препятствуя GC
Связанные темы
Ресурсы
🎓 Источник: Летняя школа 2017 — Файлы, потоки, буферы, сеть, сокеты, ошибки
- 📅 2019-11-17 · YouTube ·
Cvsv672Lbvk - Тезисы:
- EventEmitter — это ~10 строк кода. Хватает двух методов:
onиemit. Понять = написать самому on(name, fn)— если массив для этого события пустой, аллоцируется новый и в негоpush;emit(name, data)—forEachпо массиву- События — это «GoTo для JS»: передача управления куда угодно. Развязка частей программы, можно несколько подписчиков или ни одного
- Перехват всех событий (wildcard
*): сохранить оригинальныйemitв замыкание, в обёртке делать два apply — по имени и по*
- EventEmitter — это ~10 строк кода. Хватает двух методов:
- Код (минимальный):
class EventEmitter { constructor { this.events = {}; } on(name, fn) { const list = this.events[name] || (this.events[name] = ); list.push(fn); } emit(name, data) { const list = this.events[name]; if (list) list.forEach(fn => fn(data)); } } - Цитата:
«События — это GoTo для JS. Передача управления куда угодно. Место для ошибок.»