JavaScript Notes – 3 (Pub/Sub)

Posted: January 20th, 2012 | Author: | Filed under: technical | Tags: , | Comments Off

In this blog post I cover the implementation of Publisher-Subscriber (a.k.a Observer) Design Pattern in JavaScript. As the name suggests, the Pub-Sub model is a way by which an entity can publish events/messages and multiple entities can subscribe to those events/messages and subsequently take action. Browser event are examples of this pattern where the browser publishes events like keypress, mouseclick etc. and event handlers in JavaScript can have act when those events occur.
The example explained below can be seen live here. They are from the book JavaScript Patterns
The publisher, say paper which publishes a daily and a monthly magazine and a reader say, joe will be notified when that happens. A publisher object would need the following members for implementing the functionality

subscribers = [] //- an array (list) of all subscribers
subscribe = function(){} //- a method to add to the list of subscribers
unsubscribe = function(){}  //- a method to remove from the list
publish = function(){} //a method to notify all subscribers by calling the methods they provided when they signed up
//All the 3 methods above will need a type parameter as there can be many different type of events

Since these methods are generic to any publisher, it is better to implement this as a separate object. Then we can copy these properties over to any object and convert any object to a publisher (we will see this in a bit).
The code snippet below defines each of the methods and the subscribers array mentioned above.

var publisher = {
    subscribers: {
        any: [] // event type: subscribers
    }, // an object with different event types

    subscribe: function (fn, type) {
        type = type || 'any';
        if (typeof this.subscribers[type] === undefined) {
            this.subscribers[type] = [];
        }
        this.subscribers[type].push(fn);
    },
    unsubscribe: function (fn, type) {
        this.visitSubscribers('unsubscribe', fn, type);
    },
    publish: function (publication, type) {
        this.visitSubscribers('publish', publication, type);
    },
    visitSubscribers: function (action, arg, type) {
        var pubtype = type || 'any',
            subscribers = this.subscribers[pubtype],
            i,
            max = subscribers.length;
            
        for (i = 0; i < max; i += 1) {
            if (action === 'publish') {
                subscribers[i](arg);
            } else {
                if (subscribers[i] === arg) {
                    subscribers.splice(i, 1);
                }
            }
        }
    }
};

Now we have a generic publisher using which we can convert any object to a publisher. The way we do it is that we copy the function properties of the generic publisher to the object. Sweet?

function makePublisher(o) {
    var i;
    for (i in publisher) {
        if (publisher.hasOwnProperty(i) && typeof publisher[i] === "function") {
            o[i] = publisher[i];
        }
    }
    o.subscribers = {any: []};
}

Now say we have the paper object which publishes daily and monthly (2 events). So you can have the object as

var paper = {
    daily : function(){
           this.publish("Daily news publication - Breaking News Today");
    }
    monthly : function(){
           this.publish("30 Days of News!", "Monthly Type");
    }
}
// use the makePublisher method to convert this to a publisher object
makePublisher(paper);

Let’s define our subscriber object, joe

var joe = {
    drinkCoffee = function(paper){
               console.log("Reading paper" +paper);
    }
    readMonthlyDigest = function(monthly){
               console.log("Reading monthly digest"+ monthly);                  
    }
}

// Joe subscribers to the paper
paper.subscribe(joe.drinkCoffee);
paper.subscribe(joe.readMonthlyDigest, "Monthly Type");

So effectively the joe object has bound those 2 methods to those 2 events of the paper object. So whenever the paper publishes (monthly or daily), joe’s methods are called since it had subscribed to them.

// Now paper publishes
paper.daily(); // logs Reading paper Daily news publication - Breaking News Today
paper.daily();
paper.daily();
paper.monthly(); // logs Reading monthly digest 30 Days of News!

So, the joe object does not hardcode the paper object or vice-versa. The objects are loosely coupled and one can be changed without impacting the other (adding new subscribers or new publications- weekly)

The beauty of this loosely coupled pattern emerges now when we can turn joe into a publisher and the paper object into a subscriber.

makePublisher(joe);
joe.tweet = function(msg){
    this.publish(msg);
}

paper.readTweets = function(tweet){
    console.log("Someone tweeted on our blog - " + tweet);
}

joe.subscribe(paper.readTweets);
// so joe can say 
joe.tweet("The news was really boring today");

The above was an example of the Pub/Sub model in JavaScript, useful for loosely coupled objects and when you have time to architect your JavaScript solutions before jumping into code directly.