JavaScript
Completed
-
Section 1: Getting Started
-
Section 2: Core Syntax and Types
-
Section 3: Strings and Numbers in Depth
-
Section 4: Control Flow
-
Section 5: Functions
-
Section 6: Objects and Arrays
-
Section 7: Maps, Sets, and Symbols
-
Section 8: Asynchronous JavaScript
-
Section 9: Object-Oriented and Prototypes
-
Section 10: The DOM
-
Section 11: Browser APIs
-
Section 12: Modern JavaScript (ES2015-ES2025)
-
Section 13: Functional Programming Patterns
-
Section 14: Error Handling and Debugging
-
Section 15: Testing
-
Section 16: Accessibility for JavaScript Developers
-
Section 17: Internationalization and Localization
-
Section 18: Performance
-
Section 19: Node.js Fundamentals
-
Section 20: Regular Expressions
-
Section 21: Design Patterns in JavaScript
-
Section 22: Security Basics
-
Section 23: Data Structures and Algorithms in JavaScript
-
Section 24: Practical Projects
-
Section 25: More Advanced Async Patterns
-
Section 26: More Object and Class Practice
-
Section 27: Working with Dates and Internationalization
-
Section 28: Web Components
-
Section 29: More DOM and Browser Practice
-
Section 30: Build Tooling for Vanilla JavaScript
-
Section 31: More Practice Projects
-
Section 32: Interview and Algorithm Practice
-
Section 33: Error Objects (MDN Reference)
-
Section 34: TypedArrays and Binary Data
-
Section 35: Reflection and Metaprogramming (MDN Reference)
-
Section 36: More Global Functions (MDN Reference)
158: The Observer Pattern (Pub/Sub)
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
AuthBusinstance using the Pub/Sub pattern. - Create a
UserInterfacemodule that subscribes to'userLoggedIn'to update a greeting message and'userLoggedOut'to show the login form. - Create a
SessionManagermodule that subscribes to'userLoggedIn'to save a token tolocalStorageand'userLoggedOut'to clear it. - Create a
Loggermodule 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.
There are no comments for now.