JavaScript PubSub
One of the most useful implementations of software design patterns is the “PubSub” pattern. “PubSub” is a shorthand term for “Publish-Subscribe” which is a description of what the pattern is actually doing. The reason it is so useful is because it is so universal to how applications work. Basically the idea is that you have pieces of code (publishers) that say “something happened” — a user logged in, there was an error retrieving data, a button was clicked — it could be anything. Then you have pieces of code (subscribers) that run when those events occur. They are called subscribers because they subscribe to the event that gets published. PubSub implementations will also have some sort of capability for previously set subscribers to unsubscribe from an event. If that event happens again after this, the subscriber code will no longer execute.
Sometimes the terms used in PubSub pattern can vary. Sometimes “subscribers” are referred to as “listeners” because they “listen” for certain events occurring. You might recognize this in things like JavaScript’s addEventListener where you can attach callback functions to run when certain DOM events (such as button clicks and mouse hovers) occur. jQuery’s on, off, and trigger methods, are 3 methods that you can use in tandem to easily implemennt PubSub, with “on” being the subscribe functionality, “off” being unsubscribe, and “trigger” being the publish functionality. Backbone.js also uses on, off and trigger in its eventing system. Whatever the terms are and however the code is structured, ultimately these are just variations of the PubSub pattern and will nearly always follow similar ideas and principles.
A lot of examples of PubSub will use something that is pre-built, like jQuery’s methods mentioned above, or a library that implements it, but what we’re going to do in the following paragraphs is look at a raw JavaScript implementation of PubSub where we can do brodcasting (publishing) of our own custom events and attach subscribers to these events. This will give us a good understanding of how PubSub works under the hood. Our implementation will be pretty simple and it will only handle custom events that we name ourselves, but it could be taken and expanded upon to include more standard universal events such as mouse events, load events, error events, and others.
To start we will create an event channel function (object) that will have one object list where we will store the names of all of our events and the callback functions to run when those events are published. As a matter of personal preference, I’m going to be using the term “broadcast” to describe the publishing (or triggering) of our custom events. I borrowed this term from AngularJS, a client-sided MVC JavaScript framework developed by Google that I have worked with in the past. I used it when developing with AngularJS and I thought it was a good way to conceptualize what was happening. But ultimately it’s just a matter of personal preference in terminology. Subscribe and unsubscribe methods will be named “subscribe” and “unsubscribe” respectively.
So let’s start by creating our function…
function EventChannel(){
this.list = {};
}
Pretty easy. On initialization of a new event channel, our event list will begin as an empty object because nothing has been subscribed yet. Now we need to add our methods to implement PubSub, and we’ll do this on the function prototype…
function EventChannel(){
this.list = {};
}
EventChannel.prototype = {
constructor: EventChannel,
subscribe: function(name, callback) {
if(!this.list[name])
this.list[name] = [];
this.list[name].push({callback:callback});
},
unsubscribe:function(name){
if(this.list[name])
delete this.list[name];
},
broadcast: function(name){
for(var i in this.list){
if(i === name) {
var args = Array.prototype.slice.call(arguments);
args.splice(0, 1);
for(var j=0; j< this.list[name].length; j++) {
this.list[name][j].callback.apply(this, args);
}
}
}
}
};
Let’s talk about what is happening in each of these. In our subscribe method, we take a name of an event and a callback function to run when the broadcast method corresponding to the name is fired. Below is how this looks in practice. We create our event channel add an event and then broadcast the event. The callback function that we passed into our subscribe method will run when we broadcast our event.
var channel = new EventChannel();
channel.subscribe('custom1', function(){
console.log('Number 1 fired...');
});
channel.broadcast('custom1');
What if we wanted to pass arguments to a function. Well, our subscribe method is set up to handle this. The function arguments are accessed and put into an array using Array.prototype.slice.call(arguments). The first array element (which is the event name) is removed using the splice method because we won’t be needing it in our callback. So the rest of the arguments following the name get passed into our callback function sequentially.
channel.subscribe('custom2', function(one, two, three){
console.log('Number 2 fired... With arguments ' + one + ' and ' + two + ' and ' + three);
});
channel.broadcast('custom2', 'one', 'two');
Going back to our original event channel prototype, when an event name and callback is added the event list property name is added to the object and callbacks are added to the property value as an array of objects. So so far our event list would look like the following…
list = {
custom1:[
{ callback: function(){ console.log('Number 1 fired...'); } }
],
custom2: [
{ callback: function(one,two,three){ console.log('Number 2 fired... With arguments... } }
]
}
We are doing things this way so that multiple callback functions can be added to a single event. These are pushed onto the array of objects as they are added. So if we wanted to, we could add another function to run on the same event…
channel.subscribe('custom1', function(){
console.log('This is another function that is running on the custom1 event...');
});
which would transform the object into the following…
list = {
custom1:[
{ callback: function(){ console.log('Number 1 fired...'); } },
{ callback: function(){ console.log('This is another function that is running on the custom1 event...'); } }
],
custom2: [
{ callback: function(one,two,three){ console.log('Number 2 fired... With arguments... } }
]
}
Now if we broadcast the custom1 event again, both of the callback functions will execute.
Below we add another event and broadcast all of our custom events. Then we unsubscribe from one event and broadcast all of our custom events again. The event that was unsubscribed from is removed.
channel.subscribe('custom3', function(){
console.log('Number 3 fired...');
});
channel.broadcast('custom1');
channel.broadcast('custom2');
channel.broadcast('custom3');
channel.unsubscribe('custom3');
console.log('Unsubscribed from custom3... trying again....');
channel.broadcast('custom1');
channel.broadcast('custom2');
channel.broadcast('custom3');
Notice that our callback with arguments has all the arguments coming up as undefined now because we did not specify them in this last go around of broadcasting our events.
When we call unsubscribe on our event channel the entire event is removed (even if there were multiple callbacks added to the event). The code just finds the property in the list matching the name passed in to the unsubscribe method and removes that property. Our system is not yet sophisticated enough to remove individual callback functions from each event. The whole shebang is removed. But with a little more dev work and a little restructuring of code, we could definitely implement something that could do this if it was needed.
So there you have it… JavaScript PubSub. Implement it in you’re applications. You’ll be happy you did!






0 Responses to JavaScript PubSub