Day 3 of 5

Observer and Event Emitter

The Observer pattern defines a one-to-many dependency: when one object changes state, all dependents are notified. JavaScript's EventEmitter is a built-in implementation of this pattern.

typescript
// Observer pattern from scratch
interface Observer<T> {
  update(data: T): void;
}

class EventEmitter<T> {
  private observers: Observer<T>[] = [];

  subscribe(observer: Observer<T>) {
    this.observers.push(observer);
    return () => this.unsubscribe(observer); // returns unsubscribe fn
  }

  unsubscribe(observer: Observer<T>) {
    this.observers = this.observers.filter(o => o !== observer);
  }

  notify(data: T) {
    this.observers.forEach(o => o.update(data));
  }
}

// Usage
const userCreated = new EventEmitter<{ id: number; email: string }>();

userCreated.subscribe({ update: (u) => sendWelcomeEmail(u.email) });
userCreated.subscribe({ update: (u) => addToAnalytics(u.id) });

userCreated.notify({ id: 1, email: '[email protected]' });
typescript
// Node.js EventEmitter
import { EventEmitter } from 'node:events';

class OrderService extends EventEmitter {
  async createOrder(data: OrderData) {
    const order = await db.orders.create(data);
    this.emit('order:created', order);
    return order;
  }
}

const orders = new OrderService();

orders.on('order:created', (order) => {
  console.log(\`New order: \${order.id}\`);
  emailService.sendConfirmation(order.userId);
});

orders.once('order:created', () => {
  analyticsService.trackFirstOrder();
});

Exercise: Build a Pub/Sub System

  1. Create an EventBus class with on/off/emit methods
  2. Add TypeScript generics so events are type-safe
  3. Emit events from a UserService on create/update/delete
  4. Subscribe a LoggerService that records all events
  5. Test that unsubscribing stops notifications

Day 3 Summary

Finished this lesson?