Building a Pub/Sub System in JavaScript: A Simple Implementation

Pub/Sub (Publish-Subscribe) pattern is a famous design pattern. This article presents a complete JavaScript implementation

Core Features

1. Subscription Implementation
2. Publish Event
3. UnSubscribe Implemention
4. Single-Fire Subscriptions
5. Clear all Subscriptions
6. Method Chaining
7. Error Isolation

Implementation Breakdown

1. Storage Architecture

javaScript 复制代码
class PubSub {
  constructor() {
     this.events = new Map(); // event -> callback[]
   }
}

Here, we use a Map to store registered events. The keys are event names, and the values are arrays of callbacks (one-to-many). Events are added to the store when a subscriber subscribes. When publishing an event, we retrieve its callbacks and execute them sequentially—this is the core idea.

2. Subscription Implementation

javaScript 复制代码
subscribe(event, callback) {
   if (typeof event !== 'string') {
     throw new TypeError('Event name must be a string');
    }
   if (typeof callback !== 'function') {
     throw new TypeError('Callback must be a function');
   }
   const callbacks = this.events.get(event) || [];
   callbacks.push(callback);
   this.events.set(event, callbacks);
   return this;
}

We validate input types and throw errors if invalid. Returning this enables method chaining.

3. Publishing Implementation

javaScript 复制代码
publish(event, ...args) {
   const callbacks = this.events.get(event) || [];
   callbacks.forEach(callback => callback(...args));
   return this;
}

get event and run it's callback one by one.

4. Unsubscription Logic

javaScript 复制代码
unsubscribe(event, callback) {
    if (!this.events.has(event)) {
        return this;
    }
    const callbacks = this.events.get(event);
    const filteredCallbacks = callbacks.filter(cb => cb !== callback);
    if (filteredCallbacks.length === 0) {
        this.events.delete(event);
    } else {
        this.events.set(event, filteredCallbacks);
    }
    return this;
}

we will remove the event when it has no callbacks after filtered.

Single-Fire Events

javaScript 复制代码
subscribeOnce(event, callback) {
   const once = (...args) => {
      callback(...args);
      this.unsubscribe(event, once);
   };
   return this.subscribe(event, once);
}

We define a higher-order function that automatically unsubscribes itself after executing the callback.

Clear all Subscriptions

javaScript 复制代码
clear() {
   this.events.clear();
   return this;
}

use Map's clear function to clean the store.

Complete Code

javaScript 复制代码
class PubSub {
  constructor() {
     this.events = new Map(); // event -> callback[]
  }

  subscribe (event, callback) {
    if (typeof event !== 'string') {
          throw new TypeError('Event name must be a string');
      }
      if (typeof callback !== 'function') {
          throw new TypeError('Callback must be a function');
      }
      const callbacks = this.events.get(event) || [];
      callbacks.push(callback);
      this.events.set(event, callbacks);
      return this;
  }

  publish(event, ...args) {
      const callbacks = this.events.get(event) || [];
      callbacks.forEach(callback => callback(...args));
      return this;
  }

  unsubscribe(event, callback) {
      if (!this.events.has(event)) {
          return this;
      }
      const callbacks = this.events.get(event);
      const filteredCallbacks = callbacks.filter(cb => cb !== callback);
      if (filteredCallbacks.length === 0) {
          this.events.delete(event);
      } else {
          this.events.set(event, filteredCallbacks);
      }
      return this;
  }

  subscribeOnce(event, callback) {
      const once = (...args) => {
          callback(...args);
          this.unsubscribe(event, once);
      };
      return this.subscribe(event, once);
  }

  clear() {
      this.events.clear();
      return this;
  }
}