Event Bus и паттерн Observer в JavaScript (Node.js) — создание гибких событийных систем

Онлайн-тренажер Python для начинающих

Изучайте Python легко и без перегрузки теорией. Решайте практические задачи с автоматической проверкой, получайте подсказки на русском языке и пишите код прямо в браузере — без необходимости что-либо устанавливать.

Начать курс

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 }                    

Рекомендаций