पाठ 17 / 25

Observer, Pub/Sub and Event Emitters

Notify many listeners without knowing them.

Subjects and subscribers

Intent: define a one-to-many dependency so that when a subject changes, all its observers are notified automatically. The subject keeps a list of listeners and calls them; it does not know what they do. This decouples, for example, an OrderService from the email, analytics and loyalty modules that react to "order placed". Variants: Node.js EventEmitter and the DOM EventTarget (in-process events), pub/sub with a broker in between (Redis, Kafka, cloud queues), and reactive streams (RxJS observables). Watch out for memory leaks from listeners that are never removed, hard-to-trace control flow ("who reacts to this event?"), and error handling: decide whether one failing listener should affect others.

A typed event bus

Listeners subscribe and receive an unsubscribe function.

type Events = {
  'order.placed': { orderId: string; customerEmail: string; totalCents: number };
  'order.cancelled': { orderId: string; reason: string };
};

class EventBus<E extends Record<string, unknown>> {
  private listeners: { [K in keyof E]?: Array<(payload: E[K]) => void> } = {};

  on<K extends keyof E>(event: K, fn: (payload: E[K]) => void): () => void {
    (this.listeners[event] ??= []).push(fn);
    return () => { this.listeners[event] = this.listeners[event]!.filter((f) => f !== fn); };
  }

  emit<K extends keyof E>(event: K, payload: E[K]) {
    for (const fn of this.listeners[event] ?? []) {
      try { fn(payload); } catch (err) { logger.error({ err, event }, 'listener failed'); }
    }
  }
}

const bus = new EventBus<Events>();
bus.on('order.placed', (e) => mailer.sendReceipt(e.customerEmail, e.orderId));
bus.on('order.placed', (e) => analytics.track('purchase', { value: e.totalCents }));
const unsubscribe = bus.on('order.cancelled', (e) => inventory.restock(e.orderId));

bus.emit('order.placed', { orderId: 'o-1', customerEmail: 'asha@example.com', totalCents: 2599 });

Always keep the unsubscribe

In UI code, subscribe in a mount or effect hook and unsubscribe in its cleanup; forgetting this is a classic source of leaks and duplicate handlers.

त्वरित जाँच: What is a common pitfall of the Observer pattern?

  • It cannot have more than one observer
  • It requires a database
  • Listeners that are never removed, causing memory leaks or duplicate handling
  • It forces synchronous network calls
Answer

Listeners that are never removed, causing memory leaks or duplicate handling — Subscriptions need a lifecycle.