← Назад к вопросам

Какие паттерны проектирования реализует EventEmitter в Node.js?

2.0 Middle🔥 121 комментариев
#Node.js и JavaScript#Архитектура и паттерны

Комментарии (1)

🐱
claude-haiku-4.5PrepBro AI30 мар. 2026 г.(ред.)

Ответ сгенерирован нейросетью и может содержать ошибки

Паттерны проектирования в EventEmitter

EventEmitter в Node.js - это не просто механизм обработки событий, это реализация нескольких классических паттернов проектирования, которые вместе создают мощную архитектурную абстракцию.

1. Observer Pattern (Паттерн Наблюдатель)

Это основной паттерн, лежащий в основе EventEmitter:

const { EventEmitter } = require("events");

// Изменение в одном объекте автоматически уведомляет всех наблюдателей
class UserService extends EventEmitter {
  constructor() {
    super();
    this.users = [];
  }
  
  addUser(user) {
    this.users.push(user);
    // Уведомляем все заинтересованные части (observer)
    this.emit("userAdded", user);
  }
  
  deleteUser(id) {
    const user = this.users.find(u => u.id === id);
    if (user) {
      this.users = this.users.filter(u => u.id !== id);
      this.emit("userDeleted", user);
    }
  }
}

const userService = new UserService();

// Разные части приложения слушают события
// Это наблюдатели (observers)
userService.on("userAdded", (user) => {
  console.log(`[Logger] User added: ${user.name}`);
});

userService.on("userAdded", (user) => {
  console.log(`[Analytics] Track user signup: ${user.id}`);
});

userService.on("userDeleted", (user) => {
  console.log(`[Audit] User deleted: ${user.id}`);
});

// Добавление пользователя триггерит все наблюдатели
userService.addUser({ id: 1, name: "John" });

Преимущества:

  • Слабая связь - UserService не знает о логгере или аналитике
  • Масштабируемость - легко добавить новых наблюдателей
  • Разделение ответственности - каждый слушатель отвечает за одно

2. Pub/Sub Pattern (Издатель-Подписчик)

Это расширение Observer, где издатели и подписчики полностью развязаны:

const { EventEmitter } = require("events");

// Центральный почтовый ящик
class EventBus extends EventEmitter {}

const eventBus = new EventBus();

// Издатели (Publishers)
class OrderService {
  completeOrder(orderId) {
    console.log(`Order ${orderId} completed`);
    // Издатель не знает, кто его слушает
    eventBus.emit("order:completed", { orderId, timestamp: Date.now() });
  }
}

// Подписчики (Subscribers)
class EmailService {
  constructor() {
    // Подписывается на события
    eventBus.on("order:completed", (order) => {
      console.log(`[Email] Sending confirmation to customer for order ${order.orderId}`);
    });
  }
}

class InventoryService {
  constructor() {
    eventBus.on("order:completed", (order) => {
      console.log(`[Inventory] Updating stock for order ${order.orderId}`);
    });
  }
}

class ReportingService {
  constructor() {
    eventBus.on("order:completed", (order) => {
      console.log(`[Reporting] Recording sale for analytics`);
    });
  }
}

const orderService = new OrderService();
new EmailService();
new InventoryService();
new ReportingService();

orderService.completeOrder(123);
// Выведет все три подписчика, хотя OrderService их не знает

Это полная Pub/Sub система: издатель не знает подписчиков, подписчики не знают друг друга.

3. Command Pattern

Использование событий как команд для выполнения действий:

const { EventEmitter } = require("events");

class CommandBus extends EventEmitter {
  execute(command, handler) {
    this.on(command, handler);
  }
  
  dispatch(command, data) {
    this.emit(command, data);
  }
}

const commandBus = new CommandBus();

// Регистрируем команды
commandBus.execute("CreateUser", (userData) => {
  console.log(`Creating user: ${userData.name}`);
  // Логика создания
});

commandBus.execute("SendEmail", (emailData) => {
  console.log(`Sending email to ${emailData.to}`);
});

commandBus.execute("LogEvent", (event) => {
  console.log(`[LOG] ${event.message}`);
});

// Отправка команд
commandBus.dispatch("CreateUser", { name: "Alice" });
commandBus.dispatch("SendEmail", { to: "alice@example.com" });
commandBus.dispatch("LogEvent", { message: "User created" });

4. Mediator Pattern (Посредник)

EventEmitter используется как посредник между компонентами:

const { EventEmitter } = require("events");

class ChatRoom extends EventEmitter {
  constructor(name) {
    super();
    this.name = name;
    this.users = new Map();
  }
  
  addUser(user) {
    this.users.set(user.id, user);
    this.emit("user:joined", user);
  }
  
  sendMessage(fromUserId, message) {
    this.emit("message:sent", {
      from: this.users.get(fromUserId),
      text: message,
      room: this.name
    });
  }
}

class User {
  constructor(id, name, chatRoom) {
    this.id = id;
    this.name = name;
    this.chatRoom = chatRoom;
    
    // Пользователь не знает о других пользователях напрямую
    // Всё общение идёт через посредника (ChatRoom)
    chatRoom.on("user:joined", (user) => {
      if (user.id !== this.id) {
        console.log(`${this.name} sees: ${user.name} joined`);
      }
    });
    
    chatRoom.on("message:sent", (msg) => {
      console.log(`${this.name} received: ${msg.from.name}: ${msg.text}`);
    });
  }
}

const room = new ChatRoom("General");
const alice = new User(1, "Alice", room);
const bob = new User(2, "Bob", room);

room.addUser(alice);
room.addUser(bob);
room.sendMessage(1, "Hi Bob!");

Посредник (ChatRoom) управляет взаимодействием между пользователями.

5. Repository Pattern с События

Использование событий для отслеживания изменений в данных:

const { EventEmitter } = require("events");

class Repository extends EventEmitter {
  constructor(name) {
    super();
    this.name = name;
    this.data = new Map();
  }
  
  create(id, item) {
    this.data.set(id, item);
    this.emit("created", { id, item });
    return item;
  }
  
  update(id, updates) {
    const item = this.data.get(id);
    if (item) {
      const updated = { ...item, ...updates };
      this.data.set(id, updated);
      this.emit("updated", { id, from: item, to: updated });
      return updated;
    }
  }
  
  delete(id) {
    const item = this.data.get(id);
    if (item) {
      this.data.delete(id);
      this.emit("deleted", { id, item });
      return true;
    }
    return false;
  }
}

const userRepo = new Repository("users");

// Event sourcing: отслеживание всех изменений
const changelog = [];

userRepo.on("created", (event) => {
  changelog.push({ type: "CREATE", ...event, timestamp: Date.now() });
  console.log(`[Audit] Created: ${event.id}`);
});

userRepo.on("updated", (event) => {
  changelog.push({ type: "UPDATE", ...event, timestamp: Date.now() });
  console.log(`[Audit] Updated: ${event.id}`);
});

userRepo.on("deleted", (event) => {
  changelog.push({ type: "DELETE", ...event, timestamp: Date.now() });
  console.log(`[Audit] Deleted: ${event.id}`);
});

userRepo.create(1, { name: "Alice" });
userRepo.update(1, { email: "alice@example.com" });
userRepo.delete(1);

console.log("Changelog:", changelog);

6. State Machine Pattern

Использование событий для управления состояниями:

const { EventEmitter } = require("events");

class OrderState extends EventEmitter {
  constructor() {
    super();
    this.state = "pending";
  }
  
  place() {
    if (this.state === "pending") {
      this.state = "confirmed";
      this.emit("placed");
    }
  }
  
  ship() {
    if (this.state === "confirmed") {
      this.state = "shipped";
      this.emit("shipped");
    }
  }
  
  deliver() {
    if (this.state === "shipped") {
      this.state = "delivered";
      this.emit("delivered");
    }
  }
  
  cancel() {
    if (["pending", "confirmed"].includes(this.state)) {
      this.state = "cancelled";
      this.emit("cancelled");
    }
  }
}

const order = new OrderState();

order.on("placed", () => console.log("Order placed!"));
order.on("shipped", () => console.log("Package shipped!"));
order.on("delivered", () => console.log("Delivered!"));
order.on("cancelled", () => console.log("Order cancelled"));

order.place();   // Событие: placed
order.ship();    // Событие: shipped
order.deliver(); // Событие: delivered

7. Chain of Responsibility

Передача события по цепочке обработчиков:

const { EventEmitter } = require("events");

class RequestLogger extends EventEmitter {
  constructor(next) {
    super();
    this.next = next;
  }
  
  handle(request) {
    console.log(`[Logger] Request: ${request.method} ${request.path}`);
    this.emit("handled", request);
    if (this.next) this.next.handle(request);
  }
}

class AuthValidator extends EventEmitter {
  constructor(next) {
    super();
    this.next = next;
  }
  
  handle(request) {
    if (request.token) {
      console.log(`[Auth] Token valid`);
      this.emit("handled", request);
      if (this.next) this.next.handle(request);
    } else {
      console.log(`[Auth] No token`);
      this.emit("error", "Unauthorized");
    }
  }
}

class BusinessLogic extends EventEmitter {
  handle(request) {
    console.log(`[Logic] Processing request`);
    this.emit("success", { status: 200, data: "Success" });
  }
}

const logic = new BusinessLogic();
const auth = new AuthValidator(logic);
const logger = new RequestLogger(auth);

logger.handle({ method: "GET", path: "/users", token: "abc123" });

Сравнение паттернов

ПаттернИспользованиеПреимущество
ObserverОдин объект уведомляет многихСлабая связь
Pub/SubПолная развязка издателя и подписчикаМаксимальная модульность
CommandИнкапсуляция действийОтмена, повтор операций
MediatorКоординация компонентовУпрощает взаимодействие
State MachineУправление состояниямиГарантирует корректные переходы
Chain of ResponsibilityЦепочка обработчиковГибкость в обработке

Best Practices

  • Используй семантичные имена событий: user:created, не event1
  • Избегай глобальных EventEmitter - это усложняет отладку
  • Типизируй события в TypeScript для безопасности
  • Ограничивай слушателей - используй once() если событие один раз
  • Обрабатывай ошибки - добавляй on("error") обработчик

EventEmitter - это не просто механизм callback, это фундамент асинхронной архитектуры Node.js, реализующий мощные паттерны для построения масштабируемых приложений.

Какие паттерны проектирования реализует EventEmitter в Node.js? | PrepBro