Building an Event Bus With Vanilla JavaScript

2020-06-16

The Event Bus is a typical pattern in programming to decouple pieces of an app. It is a central interface to signal or react to behavior like logging or alerting. Some modules can subscribe to an event without any knowledge of the publisher component and vice-versa.

This kind of technic is commonly seen in large infrastructures as a way to provide integration between services. But even in small codebases, the Event Bus could be a practical way to provide features of general use to the whole app. A module may import the central Bus to signal the start of some task, and some other module would automatically trigger a UI loading screen.

Building an Event Bus

A viable version could be achieved very fast. Let’s start that by implementing a subscribe function that feeds a Map of subscriptions. A subscription by itself will be an array of observers functions to be called when some specific event type (also known as a topic) is published.

// event-bus.js
const subscriptions = new Map();

function subscribe (topic, observer) {
  if (!subscriptions.has(topic)) {
    subscriptions.set(topic, []);
  }
  const subscription = subscriptions.get(topic);
  subscription.push(observer);
}

export { subscribe }

Then, we implement the publish function to invoke all the observer functions associated with a topic, passing some arbitrary parameter.

// event-bus.js

// ... subscribe code

function publish (topic, payload) {
  if (!subscriptions.has(topic)) return;
  const subscription = subscriptions.get(topic);
  subscription.forEach(observer => observer(payload));
}

export { subscribe, publish }

Now, other modules became able to import subscribe and publish to take advantage of our Event Bus.

// subscriber.js
import { subscribe } from './event-bus';
subscribe('SOME_EVENT', payload => console.log(payload));
subscribe('SOME_EVENT', payload => console.log(payload));

// publisher.js
import { publish } from './event-bus';
publish('SOME_EVENT', 'hi');

Improvements and Tradeoffs

That’s it, we have an app with a working Event Bus. That might be improved to offer additional features as needed. For example, with two lines of code, subscribe could return an unsubscribe function. When called, unsubscribe removes the observer from the respective topic’s subscription.

function subscribe (topic, observer) {
  if (!subscriptions.has(topic)) {
    subscriptions.set(topic, []);
  }
  const subscription = subscriptions.get(topic);
  subscription.push(observer);

  const unsubscribe = () => subscription.splice(subscription.indexOf(observer), 1);
  return unsubscribe;  
}

Now someone can remove itself from listening like this:

// subscriber.js
import { subscribe } from './event-bus';
const unsub = subscribe('SOME_EVENT', payload => console.log(payload));
unsub(); 

// publisher.js
import { publish } from './event-bus';
publish('SOME_EVENT', 'hi'); // nothing will be printed 

Other improvements are available. There was no code to deal with exceptions in observers, or maybe we could add the ability to an observer be called immediately after subscribing with the last published payload. It is probably best to consider some proven library if you want something sophisticated. One of the best packages out there is Rxjs. The library’s Subject class is an excellent candidate to build Event Buses.

Nevertheless, it is relevant to note that choosing a event bus is not free of tradeoffs. Because of its potential to be used everywhere, a failing one would be disastrous for an app. Another issue is that as complexity and signal rate rises, the Event Bus could become a performance challenge.

That’s it, friends. I leave you with my best regards 🤗.