loading...
Cover image for PubSub with Javascript in 5 minutes or less

PubSub with Javascript in 5 minutes or less

zeeshanhyder profile image Zeeshan Hyder (ZEE) ・5 min read

Let's begin

Ok, first things first. I am not a 10xer or pro at patterns so if there is any discrepancy, please correct me. This will only improve my and your understanding of the concept.
With that little disclaimer out of the way, let's dig in.

Introduction

You might have heard the term PubSub being thrown around alot (especially since Angular made Reactive pattern/event-driven pattern famous in front-end world) or maybe you just stumbled here by accident. Either way, if you are wondering what this PubSub mumbo-jumbo is, this post will help you understand the concept a little better, along with a basic implementation with VanillaJS (Sorry I really love things simple!).

What is PubSub?

PubSub or Publisher-Subscriber model is a concept which basically involves two ends. Publisher and Subscriber. Or in very simple terms: Giver and Taker. This should tell you something about flow of data in the pattern. Yup, that is right. Publisher will have some data it needs to give (let's not worry about where it got that from right now) which Subscribers take and may:

  • display,
  • manipulate,
  • post-process or
  • do black magic with. I don't know, that's not really my concern.

Well what if the data is not needed by just one person/receiver. Maybe it's a common thing that many people want. Let's say, you are driving down Route 666 and there is a crash 500 metres down. It's unlikely that it's just you alone on 666 (if you are, good luck!), there maybe few others driving along. Imagine how useful this information would be to all of them. Right? Now, let's suppose before getting on Route 666, all of you signed up for this service that gives you updates about events happening along Route 666 until you get off the highway(unsubscribe). So now, all of the people who signed up are Subscribers and your service-provider who you signed up with is your Publisher. Notice how the word event got in there? Well the information is usually sent by Publisher after some "event"(occurs). PubSub is not one-off model. Usually it's an ongoing process. Anyway you get the gist, right? So what did we find out?

PubSub Model

PubSub Model

Discovery

  1. PubSub has two ends: Publisher (Giver) and Subscriber (Taker).
  2. PubSub talks in terms of events.
  3. PubSub has single Publisher (origin of events) and multiple subscribers(culmination of events).
  4. PubSub is on-going model rather than one-off. (You receive events overtime).
  5. You sign-up (subscribe) to receive information and sign-off (unsubscribe) to stop receiving further information.

Now let's take these concepts and create a basic working model with good ol' Javascript.
I'll be using ES6 class with private members workaround (Javascript ES6 doesn't natively support private).

Code Example

First let us build the barebones structure of our Publisher-Subscriber module. We will take a class based approach which will contain all the pubsub logic as well as any data structure needed. The list below illustrates what we need to accomplish and then we'll build out a barebones structure from that.

Requirements

  1. Some event (fake) source to generate data. (In real world this might generate from real-world events but for the purposes of this example, we'll fake it).
  2. Something to hold our subscriber list so that we know who we need to send data to (Data).
  3. Someway to register/deregister subscribers (Methods).
  4. Someway to send data to all the subscribers (Logic).

Barebones

Data

To store metadata in our PubSub model, we'll use following variables:

  1. private eventSourceAddress: In real world, this would be your WebSockets, Server Sent events or any other real-time source address. We will just initialize it in our code and not connect to actual backend.
  2. private subscribers: Object array to hold our subscriber list. You are very welcome to find out more optimal method, but that is not the focus of this article.

Methods

  1. private _addSubscriber(): Adds subscriber to the list.
  2. private _removeSubscriber(): Removes subscriber from the list.
  3. private _removeAllSubscribers(): Clears out the subscriber list.
  4. public subscribe(): Available to instantiated class that internally calls _addSubscriber.
  5. public unsubscribe(): Available to instantiated class that internally calls _removeSubscriber.
  6. public unsubscribeAll(): Available to instantiated class that internally calls _removeAllSubscribers.

Logic

  1. private _pushPayloadToSubscribers(): Pushes data to all subscribers.

Additional methods

Since we will faking event source, we need additional data and methods to enable that. These are not part of actual Publisher-Subscriber model.

Data
  1. private __tick: Holds the current reference to event source (timer).
Method
  1. private __tickHandler(): Executed after certain interval to send data to publisher.
  2. public plugSource(): Plug in event source and start feed.
  3. public unplugSource(): Unplug event source and stop feed.

Ok, looks like we have all the ingredients ready, now let's go ahead and build our PubSub model.

Implementation

const PubSub = (function(){
    // private variables and data
    return class _PubSubInternal{
        // public methods and data
    }
})();

What the hell was that?

Sorry, as I said, Javascript natively doesn't support private accessors, we have to use this little workaround to have "private" variables in our class. What we are doing is using IIFE and Closures.
The outer function executes immediately as the script is processed and the function inside it gets called and it returns _PubSubInternal.
Since we are returning _PubSubInternal from inside the function, all the variables and methods declared inside the function will be accessible to this returned class (but not to instantiated object of this class). This is called closure. Pretty cool, right?!

Javascript is pretty awesome!

Anyway, moving on.

    const PubSub = (function(){
        // private variables and data
        let eventSourceAddress;
        let subscribers;
        let __tick;

        function __tickHandler() {
            _pushPayloadToSubscribers(new Date());
        }

        function _pushPayloadToSubscribers(payload) {
            subscribers.map(subscriber => {
                subscriber.callback(payload);
            });
        }

        function _addSubscriber(callback) {
            var id = new Date().getTime();
            subscribers.push({ id, callback });
            return id;
        }

        function _removeSubscriber(id) {
            subscribers = subscribers.filter(subscriber => subscriber.id !== id);
        }

        function _removeAllSubscribers() {
            subscribers = [];
        }

        return class _PubSubInternal{
            // public methods and data
            constructor(address) {
                eventSourceAddress = address;
                subscribers = [];
                __tick = null;
            }

            plugSource() {
                console.log("Event feed attached!");
                __tick = window.setInterval(__tickHandler.bind(this), 4000);
            }

            unplugSource() {
                window.clearInterval(__tick);
                console.log("Event feed unplugged!");
            }

            subscribe(callback) {
                return _addSubscriber(callback);
            }

            unsubscribe(id) {
                return _removeSubscriber(id);
            }

            unsubscribeAll() {
                return _removeAllSubscribers();
            }

        }
    })();

And that is it. This completes our implementation of our Publisher-Subscriber model.

Run it

var pubSub = new PubSub(someEventSourceAddress);

// add subscribers
let subscriber1 = pubSub.subscribe( (data) => {
    console.log("Consoling from Subscriber 1");
    console.log(data);
});

let subscriber2 = pubSub.subscribe( (data) => {
    console.log("Consoling from Subscriber 2");
    console.log(data);
});

// Start fake feed
pubSub.plugSource();

We instantiated our PubSub to pubSub variable which holds reference to PubSub object. subscriber1 and subscriber2 hold the Resource ID of their subscription (subscription id) which can be used later on to unsubscribe. Callbacks get executed whenever new data is pushed to these subscriber. The sample output is below.

// Output

// subscriber 1 subscribed
// subscriber 2 subscribed
> Event feed attached!

// after 4 secs
> Consoling from Subscriber 1
> Sun Aug 04 2019 17:44:44 GMT-0400 (Eastern Daylight Time)

> Consoling from Subscriber 2
> Sun Aug 04 2019 17:44:44 GMT-0400 (Eastern Daylight Time)
...
...

To stop subscription and feed, just do:

pubSub.unsubscribe(subscriber1);
pubSub.unsubscribe(subscriber2);
// OR
pubSub.unsubscribeAll();

// Stop feed
pubSub.unplugSource(); // Event feed unplugged!

And that is it folks! Hope you enjoyed the article and learned something new :)

You can tinker around with the implementation on codesandbox

Good luck!

Discussion

pic
Editor guide