JavaScript Design Patterns Part 2: The Publisher/Subscriber Pattern

Babs Craig
6 min readNov 15, 2018

--

Photo by Oleg Laptev on Unsplash

In Part 1 of this series, we looked at the Factory Pattern which we described as a creational pattern, the purpose of which was to allow us easily create objects while abstracting away details of their implementation. In this article, we will look at the first of the behavioral patterns that we’ll be covering in this series called the Publisher/Subscriber pattern.

As always, the code for this series can be found on Github.

The Publisher/Subscriber pattern, or “PubSub” for short, is a pattern that allows us to create modules that can communicate with each other without depending directly on each other. It’s a great pattern for decoupling our application and is quite common in JavaScript.

A word of caution — use this pattern wisely as it is easy for it to be overused. Some amount of coupling is necessary in order to have readable and maintainable code and it is easy to decouple your code to the extent that the lack of dependencies makes it hard to reason about.

The PubSub Pattern In Action

In the javascript-design-patterns folder we created the last time, add a new folder called pubsub.

We’ll start with our pubsub module. Create a file called pubsub.js and add the following code:

let subscribers = {};module.exports = {
publish() {
// method to publish an update
},
subscribe() {
// method to subscribe to an update
}
};

First off, we create an object, which we’ll call subscribers, to keep track of registered subscriber callbacks. Inside this object, we will eventually store events key/value pairs. Each event will have a key corresponding to the event name and a value set to an array. In this array, we will register/store subscriber callbacks. These callbacks are invoked whenever the event gets triggered and we may have several of such callbacks firing for any given event.

Next, the pubsub module exports two functions. One to ‘publish’ an update and another to ‘subscribe’ to updates.

This is the bare bones of our publisher/subscriber module.

Let’s focus on the subscribe method first since this is the method other modules will use to register a subscriber callback.

Our subscribe method will accept two arguments. The first is the event name being subscribed to and the second is the callback to be invoked when the event is published.

subscribe(event, callback) {
if (!subscribers[event]) {
subscribers[event] = [];
}
subscribers[event].push(callback);
}

In the above bit of code, we first check to see if the event in question has been registered in the subscribers object. If the event does not exist on the subscribers object, we know it hasn’t been registered and so we register it using the event name as the key and we initialize the value to an empty array. Lastly, we’ll push the subscriber callback into the event array.

Next, the publish method:

publish(event, data) {
if (!subscribers[event]) return;
subscribers[event].forEach(subscriberCallback =>
subscriberCallback(data));
}

First, we check if there are any subscribers that have registered for the event. If there aren’t, we can return early from the publish method since we have no subscribers with callbacks to invoke. If there are subscribers, we loop through the events array and invoke each subscriber callback that has been pushed into the event array. We also pass through any data that may have been provided to each of the callbacks.

Note that the data parameter is optional and may or may not be provided.

Now we can create our modules which will consume the pubsub object. Add two new files called moduleA.js and moduleB.js respectively.

In this example, moduleA will be the publisher and moduleB will be the subscriber. As we will see, both modules will have no knowledge of each other and will communicate only via the pubsub module.

moduleA.js:

const pubSub = require("./pubsub");module.exports = {
publishEvent() {
const data = {
msg: "TOP SECRET DATA"
};

pubSub.publish("anEvent", data);
}
};

Here, we require the pubSub module and we export an object with a publishEvent method. The method specifies some data and then calls the pubSub’s publish method passing in both the event name we are publishing and the data object we created.

moduleB.js:

const pubSub = require("./pubsub");pubSub.subscribe("anEvent", data => {
console.log(
`"anEvent", was published with this data: "${data.msg}"`
);
});

The subscriber’s code is more concise. Here we require the pubSub module and invoke the subscribe method to subscribe to an event. We pass in the event name that we wish to subscribe to and a subscriber callback. This is the callback that gets stored in the events array and will be invoked when an event is published. As we saw earlier, it will also provide us with an optional data object and in this case, we simply log the data.msg property to the console.

Now we can set up our entry file. In the same folder include an index.js file.

const moduleA = require("./moduleA");
const moduleB = require("./moduleB");
// We use moduleA's publishEvent() method
moduleA.publishEvent();
moduleA.publishEvent();

Here we require both the modules and then we invoke the publishEvent method from moduleA.

Now in your terminal navigate to the javascript-design-patterns folder and type:

$ node ./pubsub/index.js

You should see the following logged to the console:

"anEvent", was published with this data: "TOP SECRET DATA"
"anEvent", was published with this data: "TOP SECRET DATA"

Taking this code from the top, what happened is that when we required moduleB — our subscriber, the pubSub's subscribe method created an event called “anEvent” and added the callback we provided to the list of callbacks for that event.

When we eventually call the publishEvent method of moduleA — our publisher, the callback is invoked. Since we call the publishEvent method twice, we see the data.msg property logged to the console twice.

At this point, there’s one more requirement missing from a full implementation of the PubSub pattern, and that is a way for subscribers to unsubscribe from updates.

A great way to do this is to return an object with an unsubscribe method from the function call that subscribes a module to an event.

We need to know the index of the subscriber callback within the event array, so we can remove it. In our pubsub.js file, let’s refactor the subscribe method in the pubsub.js file.

subscribe(event, callback) {
let index;
if (!subscribers[event]) {
subscribers[event] = [];
}
index = subscribers[event].push(callback) - 1;

return {
unsubscribe() {
subscribers[event].splice(index, 1);
}
};
}

We’ll add a new variable called index. Luckily for us, the Javascript push() method returns the new length of the array after adding the pushed item. We can take this length and subtract 1 in order to get the index of the newly added subscriber callback which we’ll then store in the index variable.

After this, we return an object which contains anunsubscribe method. This method takes the subscribers[event] array and removes the callback at the specified index location using Javascript’s splice method.

We can now use this unsubscribe method in our subscriber. In moduleB.js:

const pubSub = require("./pubsub");
let subscription;
subscription = pubSub.subscribe("anEvent", data => {
console.log(
`"anEvent", was published with this data: "${data.msg}"`
);
subscription.unsubscribe();
});

First, we add a subscription variable. Then we store the return value from the pubSub.subcribe() method in the subscription variable (which from the pubsub module we know to be an object with an unsubscribe method).

Then we call the unsubscribe method in the callback after logging the event to the console.

If you run the program again, this time around you should find that there is only one message logged to the console. This is because our subscriber has unsubscribed from updates by the time the second publishEvent() method runs using the subscription.unsubscribe() method.

And that’s it for the Publisher/Subscriber pattern!

To summarize, we created a pubsub module, responsible for providing the means of subscribing and publishing updates via the subscribe and publish methods respectively. The communication takes place over a named channel, which we referred to as “events” and each event keeps track of subscriber callbacks which are invoked when the event is triggered/published. We also created an unsubscribe() method that allows code to clean up after itself when further updates are no longer required.

As we’ve seen, the Publisher/Subscriber pattern makes it easy to decouple our modules and remove hard dependencies between them. However, as noted earlier, we must be careful not to overuse this pattern as it can lead to obscure, hard to maintain code.

Next up in the series, we’ll be covering the Strategy pattern. A pattern for selecting different behaviours (strategies) at runtime depending on the input provided. To stay notified be sure to give me a follow and if you’ve found this article helpful, do leave a thumbs up (or 10 😃). As always, I’d love to hear your thoughts in the comments below!

Babs is a JavaScript developer who writes mostly React/React Native & NodeJS (with a healthy dose of GraphQL) by day and everything else JavaScript under the cover of night. You can find him on Twitter and Instagram where he shares details of his clandestine love affair with JavaScript.

--

--

Babs Craig

Remote Software Developer 💻 | Building React & React Native apps with G2i.co | Trying to write much more | Audiophile