To implement an efficient PubSub (Publish-Subscribe) system in JavaScript, use a Map-based structure to manage subscribers. This approach ensures optimal performance for both subscribing/unsubscribing (O(1) complexity) and publishing (O(n) complexity, unavoidable). Here's the implementation:
class PubSub {
constructor() {
this.events = new Map(); // Event -> Subscribers (Map: ID -> Callback)
}
subscribe(event, callback) {
if (typeof callback !== 'function') {
throw new Error('Callback must be a function');
}
// Get or create the event's subscriber map
let subscribers = this.events.get(event);
if (!subscribers) {
subscribers = new Map();
this.events.set(event, subscribers);
}
// Use a unique Symbol as the subscriber ID
const id = Symbol();
subscribers.set(id, callback);
// Return an unsubscribe function
return () => subscribers.delete(id);
}
publish(event, data) {
const subscribers = this.events.get(event);
if (subscribers) {
// Call all subscribers in insertion order
subscribers.forEach(callback => {
try {
callback(data);
} catch (error) {
console.error(`Error in subscriber for ${event}:`, error);
}
});
}
}
}
-
O(1) Subscription/Unsubscription
- Uses a nested
Map
(events
→subscribers
) for direct access to subscribers via uniqueSymbol
IDs. - Unsubscribing removes the callback instantly without iterating through a list.
- Uses a nested
-
Order Preservation
Map
maintains insertion order, ensuring subscribers are called in the order they subscribed.
-
Error Handling
- Catches errors in subscribers to prevent one faulty callback from breaking others.
-
Memory Efficiency
- No duplicate entries for the same callback unless explicitly subscribed multiple times.
const pubsub = new PubSub();
// Subscribe
const unsubscribe = pubsub.subscribe('message', (data) => {
console.log('Received:', data);
});
// Publish
pubsub.publish('message', 'Hello World!'); // Logs "Received: Hello World!"
// Unsubscribe
unsubscribe();
// No output after unsubscribing
pubsub.publish('message', 'Hello again!');
- Dynamic Subscriptions: Ideal if your app frequently adds/removes subscribers.
- Order-Sensitive Events: Subscribers are always called in the order they subscribed.
- High Performance: Optimized for both subscription management and event emission.
- Array-Based Subscribers: Slightly faster iteration for publishing but slower unsubscription (O(n)).
- Third-Party Libraries: Use existing solutions like
EventEmitter
(Node.js) orRxJS
for advanced use cases (e.g., streams, filters).
This implementation balances speed, memory, and simplicity, making it the most efficient way to handle PubSub in vanilla JavaScript.