Skip to Content
Course content

158: The Observer Pattern (Pub/Sub)

Click on the "Edit" button in the top corner of the screen to edit your slide content.

I remember working on a checkout flow a few years back that started out simple. We had a completePurchase() function that did exactly what you'd expect: it hit the API and redirected the user to a thank-you page. But then, the requirements started piling up. Marketing wanted a tracking pixel triggered on success. The UI team wanted a confetti animation. The backend team wanted an audit log entry sent to a different service. Before I knew it, that one function was 200 lines of imports and direct function calls to five different modules. The worst part? If the confetti animation library threw an error, the entire checkout process crashed, and the user never saw their confirmation page.

This is what we call "tight coupling," and it's a nightmare to maintain. The checkout logic shouldn't have to know that a confetti library exists. It should just announce that a purchase happened and let other parts of the system react however they want. That's where the Observer pattern—specifically the Publish/Subscribe (Pub/Sub) variation—saves your sanity.

Breaking the Tight Coupling Chain

In a Pub/Sub architecture, we introduce a middleman called an Event Bus or a Broker. Instead of the "Publisher" calling the "Subscriber" directly, the Publisher just sends a message to the bus with a specific topic (or event name). The Subscriber tells the bus, "Hey, let me know whenever something happens on this topic."

This flips the dependency. Now, your completePurchase() function doesn't depend on the Analytics module or the UI module; it only depends on the Event Bus. If you decide to delete the confetti animation tomorrow, you don't even have to touch the checkout code. You just remove the subscriber. I've found this is the single most effective way to keep a growing JavaScript codebase from turning into a bowl of spaghetti.

Building a Simple Event Bus

You don't need a heavy library to do this. A basic Pub/Sub system is essentially just an object that maps event names to arrays of callback functions. Here is how I usually implement a lightweight version:

class EventBus {
  constructor() {
    this.topics = {};
  }

  subscribe(topic, listener) {
    if (!this.topics[topic]) {
      this.topics[topic] = [];
    }
    this.topics[topic].push(listener);

    // Return an unsubscribe function for easy cleanup
    return () => {
      this.topics[topic] = this.topics[topic].filter(l => l !== listener);
    };
  }

  publish(topic, data) {
    if (!this.topics[topic]) return;
    this.topics[topic].forEach(listener => listener(data));
  }
}

const bus = new EventBus();

// Subscriber 1: Analytics
bus.subscribe('orderCompleted', (order) => {
  console.log(`Sending order ${order.id} to Google Analytics...`);
});

// Subscriber 2: UI Notification
bus.subscribe('orderCompleted', (order) => {
  console.log(`Showing confetti for order ${order.id}! 🎉`);
});

// The Publisher: Doesn't know who is listening
function completePurchase() {
  const order = { id: 123, total: 59.99 };
  console.log('Processing payment...');
  bus.publish('orderCompleted', order);
}

completePurchase();

Notice the return value in the subscribe method. In a real-world app—especially if you're using frameworks like React or Vue—forgetting to unsubscribe when a component unmounts is a fast track to memory leaks. Always give your subscribers a way to opt-out.




📋 Practical Task

Exercise: Building a Multi-Module User Authentication System

You are building a user authentication system where several unrelated parts of the app need to react when a user logs in or logs out. Instead of calling every module manually, you will implement a Pub/Sub system.

Your requirements:

  • Create an AuthBus instance using the Pub/Sub pattern.
  • Create a UserInterface module that subscribes to 'userLoggedIn' to update a greeting message and 'userLoggedOut' to show the login form.
  • Create a SessionManager module that subscribes to 'userLoggedIn' to save a token to localStorage and 'userLoggedOut' to clear it.
  • Create a Logger module that subscribes to both events to log the timestamp of the activity to the console.
  • Implement a login(username) function that publishes the 'userLoggedIn' event with the username as data.
  • Implement a logout() function that publishes the 'userLoggedOut' event.

Test your implementation by calling login('Dev_User') and verifying that the UI, SessionManager, and Logger all react independently.

Rating
0 0

There are no comments for now.

to be the first to leave a comment.