Event-driven архитектура на JavaScript: Как писать реактивные приложения

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

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

Начать курс

Event-driven архитектура на JavaScript: Почему ваш код должен реагировать, а не ждать

Вы когда-нибудь задумывались, почему современный JavaScript работает так быстро и гибко? Ответ кроется в одном слове — события. Event-driven архитектура (событийно-ориентированная архитектура) — это не просто модный термин, а фундамент, на котором построены браузеры, Node.js и практически все современные веб-приложения.

Представьте себе ресторан: традиционный код работает как «обслуживание по одному» — официант принимает заказ, идёт на кухню, ждёт, пока приготовят, несёт еду, и только потом подходит к следующему столику. Event-driven архитектура — это когда официант раздаёт заказы, вешает номерок, идёт к следующему клиенту, а еда «приходит сама» по готовности. Эффективно? Безусловно.

В этой статье мы разберём, как устроена событийно-ориентированная архитектура в JavaScript, напишем собственный EventEmitter и рассмотрим реальные примеры, которые сделают ваш код чище и быстрее. Поехали!

Что такое Event-driven архитектура и зачем она нужна?

Event-driven архитектура (EDA) — это парадигма программирования, в которой поток выполнения программы определяется событиями: действиями пользователя, сообщениями от других сервисов, изменениями данных или системными сигналами. Вместо того чтобы постоянно «опрашивать» состояние, мы подписываемся на события и реагируем на них.

Ключевые компоненты EDA

  • Событие (Event) — сигнал о том, что что-то произошло (клик, таймаут, получение данных).
  • Эмиттер (Emitter) — объект, который генерирует события.
  • Слушатель/Обработчик (Listener/Handler) — функция, которая реагирует на событие.
  • Канал событий — механизм доставки событий от эмиттера к слушателям.

В JavaScript эта архитектура реализуется через паттерн Observer (Наблюдатель) и встроенный EventEmitter в Node.js. Браузеры также используют EDA через систему DOM-событий и addEventListener.

Почему это важно для вашего кода?

  • Асинхронность: не блокируем поток выполнения.
  • Слабая связанность: компоненты не зависят друг от друга напрямую.
  • Масштабируемость: легко добавлять новые обработчики.
  • Понятность: код становится декларативным — «когда X, сделай Y».

Основы Event-driven подхода: от DOM до Node.js

Давайте начнём с самого простого — событий в браузере. Каждый раз, когда вы пишете button.addEventListener('click', handler), вы используете Event-driven архитектуру. Но это лишь верхушка айсберга.

Пример 1: Простая обработка событий в браузере

// Подписываемся на событие клика
document.getElementById('myButton').addEventListener('click', (event) => {
    console.log('Кнопка нажата!', event.target);
});

// Генерируем своё событие
const customEvent = new CustomEvent('userLogin', { 
    detail: { username: 'pythonlib_user' } 
});
document.dispatchEvent(customEvent);

// Слушаем кастомное событие
document.addEventListener('userLogin', (event) => {
    console.log(`Пользователь ${event.detail.username} вошёл в систему`);
});

Обратите внимание: мы не вызываем функцию напрямую, а говорим системе: «Когда произойдёт это событие — сделай то». Это и есть суть Event-driven подхода.

Пример 2: EventEmitter в Node.js

Node.js имеет встроенный модуль events, который предоставляет класс EventEmitter. Это сердце многих библиотек и фреймворков.

const EventEmitter = require('events');

// Создаём эмиттер
const myEmitter = new EventEmitter();

// Подписываемся на событие 'data'
myEmitter.on('data', (chunk) => {
    console.log(`Получен кусок данных: ${chunk}`);
});

// Генерируем событие
myEmitter.emit('data', 'Первый кусок');
myEmitter.emit('data', 'Второй кусок');

// Одноразовая подписка
myEmitter.once('error', (err) => {
    console.error(`Критическая ошибка: ${err.message}`);
});

В реальных проектах EventEmitter используется для логирования, обработки потоков данных, управления состояниями и в архитектуре микросервисов.

Создаём свой EventEmitter: практический пример

Чтобы глубже понять Event-driven архитектуру, напишем собственную реализацию EventEmitter. Это поможет вам понять, как работают под капотом библиотеки вроде RxJS или Redux.

Базовая реализация

class SimpleEventEmitter {
    constructor() {
        this.events = {}; // Хранилище событий и их обработчиков
    }

    // Подписка на событие
    on(eventName, listener) {
        if (!this.events[eventName]) {
            this.events[eventName] = [];
        }
        this.events[eventName].push(listener);
        return () => this.off(eventName, listener); // Возвращаем функцию отписки
    }

    // Одноразовая подписка
    once(eventName, listener) {
        const wrapper = (...args) => {
            listener(...args);
            this.off(eventName, wrapper);
        };
        this.on(eventName, wrapper);
    }

    // Генерация события
    emit(eventName, ...args) {
        const listeners = this.events[eventName];
        if (listeners) {
            listeners.forEach(listener => listener(...args));
        }
    }

    // Отписка от события
    off(eventName, listener) {
        const listeners = this.events[eventName];
        if (listeners) {
            this.events[eventName] = listeners.filter(l => l !== listener);
        }
    }
}

// Использование
const emitter = new SimpleEventEmitter();
const unsubscribe = emitter.on('greet', (name) => {
    console.log(`Привет, ${name}!`);
});

emitter.emit('greet', 'PythonLib'); // Привет, PythonLib!
unsubscribe(); // Отписываемся
emitter.emit('greet', 'Мир'); // Ничего не произойдёт

Всего ~30 строк кода, а перед вами полноценная реализация паттерна Observer. Теперь вы понимаете, как работают события внутри.

Event-driven архитектура в реальных проектах

Теория теорией, но где это применяется на практике? Давайте рассмотрим несколько сценариев.

Сценарий 1: Реактивный UI на чистом JavaScript

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

class NoteApp {
    constructor() {
        this.emitter = new SimpleEventEmitter();
        this.notes = [];
    }

    addNote(text) {
        const note = { id: Date.now(), text, createdAt: new Date() };
        this.notes.push(note);
        // Генерируем событие
        this.emitter.emit('noteAdded', note);
    }

    deleteNote(id) {
        this.notes = this.notes.filter(n =>                

Блоги

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