Event Bus и паттерн Observer в JavaScript (Node.js)
В современной разработке на Node.js часто возникает необходимость организовать взаимодействие между различными компонентами приложения, не создавая жёстких связей между ними. Паттерн Observer (Наблюдатель) и его более мощная реализация — Event Bus (Шина событий) — предоставляют элегантное решение этой задачи. В этой статье мы подробно разберём, как реализовать эти паттерны на чистом JavaScript в среде Node.js, рассмотрим их преимущества и типичные сценарии использования.
Что такое паттерн Observer?
Паттерн Observer определяет отношение "один ко многим" между объектами: когда один объект (субъект) изменяет своё состояние, все зависимые от него объекты (наблюдатели) автоматически уведомляются об этом. В контексте Node.js этот паттерн лежит в основе встроенного модуля events, который предоставляет класс EventEmitter.
Event Bus: расширение концепции
Event Bus — это централизованная шина событий, которая позволяет любому компоненту приложения отправлять и получать сообщения, не зная друг о друге напрямую. Это особенно полезно в микросервисной архитектуре, сложных приложениях с модульной структурой или при создании плагинных систем.
Реализация Event Bus на Node.js
Рассмотрим создание собственного Event Bus с нуля. Наш класс будет поддерживать основные методы: подписка на события (on), одноразовая подписка (once), удаление подписчиков (off) и эмиссия событий (emit).
class EventBus {
constructor() {
this.events = {};
}
on(event, listener) {
if (!this.events[event]) {
this.events[event] = [];
}
this.events[event].push(listener);
return () => this.off(event, listener);
}
once(event, listener) {
const wrapper = (...args) => {
listener(...args);
this.off(event, wrapper);
};
this.on(event, wrapper);
return () => this.off(event, wrapper);
}
off(event, listener) {
if (!this.events[event]) return;
this.events[event] = this.events[event].filter(l => l !== listener);
}
emit(event, ...args) {
if (!this.events[event]) return false;
this.events[event].forEach(listener => listener(...args));
return true;
}
}
// Пример использования
const bus = new EventBus();
const unsubscribe = bus.on('user:login', (username) => {
console.log(`Пользователь ${username} вошёл в систему`);
});
bus.once('app:start', () => {
console.log('Приложение запущено (однократное уведомление)');
});
// Эмиссия событий
bus.emit('app:start'); // "Приложение запущено (однократное уведомление)"
bus.emit('user:login', 'Анна'); // "Пользователь Анна вошёл в систему"
// Отписка
unsubscribe();
bus.emit('user:login', 'Иван'); // Ничего не произойдёт
Использование встроенного EventEmitter из Node.js
Node.js предоставляет готовую реализацию EventEmitter в модуле events. Однако Event Bus, созданный на его основе, позволяет централизованно управлять всеми событиями приложения.
const EventEmitter = require('events');
class AppEventBus extends EventEmitter {
constructor() {
super();
this.setMaxListeners(50); // Увеличиваем лимит подписчиков
}
// Добавляем логирование для отладки
emit(event, ...args) {
console.log(`[EventBus] Событие: ${event}`);
return super.emit(event, ...args);
}
}
const appBus = new AppEventBus();
// Подписка на события
appBus.on('order:created', (order) => {
console.log(`Заказ #${order.id} создан. Отправляем уведомление.`);
});
appBus.on('order:created', (order) => {
console.log(`Заказ #${order.id} создан. Обновляем склад.`);
});
// Эмиссия
appBus.emit('order:created', { id: 123, items: ['ноутбук', 'мышь'] });
Продвинутая реализация с поддержкой пространств имён
Для крупных приложений удобно использовать пространства имён (namespaces), чтобы группировать события по модулям или функциональным областям.
class NamespacedEventBus {
constructor() {
this.namespaces = {};
}
namespace(name) {
if (!this.namespaces[name]) {
this.namespaces[name] = new EventEmitter();
}
return this.namespaces[name];
}
}
const bus = new NamespacedEventBus();
const userEvents = bus.namespace('user');
const orderEvents = bus.namespace('order');
userEvents.on('login', (user) => console.log(`User: ${user.name} logged in`));
orderEvents.on('created', (order) => console.log(`Order: ${order.id} created`));
// Эмиссия в разных пространствах
userEvents.emit('login', { name: 'Alice' });
orderEvents.emit('created', { id: 456 } Также в библиотеке
Машинное обучение на Java: обзор ключевых библиотек
OAuth клиент для Kotlin: интеграция с провайдерами через ktor-client и kotlinx-serialization
Работа с S3 и облачными хранилищами в C++: библиотека AWS SDK для C++
Тестирование в C++: обзор фреймворков и инструментов (Google Test, Catch2, doctest)
Тестирование в C# (.NET): полный обзор фреймворков и инструментов