Understanding Javascript Publish/Subscription

One of the most interesting design patterns used on web development is the Publish/Subscribe. This pattern, also known as Pub/Sub, fits like a glove on the browser, mainly because the DOM is event driven, it uses events as its main interaction with API scripts. However, events aren not restricted to the DOM and you can have your own events on your API.

Pub/Sub offers a way to handle communication between various parts of a whole system. In short, subscribers want to be notified when something interesting happens and publishers want to notify, to those interested, that something has happened.

Pub/Sub diagram

Pub/Sub diagram

So this is the idea, a client 1 say for instance “I want to be notified when the data changes” and client 2 says “The data has changed!”.
Now that client 1 has been notified, he can update the data on the page for instance.

Without Pub/Sub, this works as well, the main difference is that the publisher needs to know about the other clients. In this case, after data changes, client 2 needs to call directly client 1, which would sound like “Hi client 1, data has changed!”.
This means client 1 and client 2 are tightly connected. This scenario starts to be complex with the number of clients growing, making the maintenance of your code too dificult. And what if one client is not there or is not right implemented? Will the other clients be afected?

What Pub/Sub introduces is loose coupling, the publisher doesn’t need to know who are the clients or how many are they. Neither the subscribers need to know about publishers or other subscribers. If one fails the other ones will keep working normaly.

On the other hand this decoupling also presents as its disadvantage, mainly because the publisher and subscribers don’t have any guarantee that everything is working, there is no guarantee that subscribers will be notified neither publishers have the guarantee that anyone is listening.

Event though I think the advantages are much more valuable than the disadvantage, however, the Pub/Sub pattern doesn’t apply to every application, and there are situations more suitable than others.

For instance, this pattern is commonly used on MVC applications (or some variation), where the View subscribes to changes on the models, and react properly when the Model notifies about its changes.

Normaly Javascript frameworks and libraries, and not necessarly MVC frameworks, implement this pattern and lets you use it out of the box, for instance, if you are using jQuery you can use Custom Events.

However, I want to show how easy is to implement your own version of Pub/Sub so that you understand even better what happens.

Below you can see my tiny implementation and I hope the code and comments will be self explanatory.


/*
* Pub/Sub implementation
*/

var pubsub = (function () {

 // store events subscribed
 // events = { eventName : [callback1, callback2, ...] }
 var events = {};

 // publish the event with the specified data
 function publish(eventID, data) {

   if (!events[eventID]) {
     return;
   } 

   var len = events[eventID].length;

   while (len--) {
     events[eventID][len](data);
   }

 }

 // subscribe to an event
 // passing the event name and the callback function
 function subscribe(eventID, callback) {

   if (!events[eventID]) {
     events[eventID] = [callback];
   } else {
     events[eventID].push(callback);
   }

   // returns eventID and the position index on the array
   // so that can be unsubscribed if needed
   return {
     eventID : eventID,
     index : events[eventID].length - 1
   }
 }

 // unsubscribe from an event passing the returned data got upon subscription
 function unsubscribe(subscription) {

   if (!events[subscription.eventID]) {
     return;
   } 

   events[subscription.eventID].splice(subscription.index, 1);
 }

 return {
   publish : publish,
   subscribe : subscribe,
   unsubscribe : unsubscribe
 };

}());

And now, a short example of how this can be used. We have a list of items that we can add to the basket and we have two different vies of the basket, a simple one that shows only the total items and a detailed one where shows the items and quantities. When an item is added to the basket, the views will be notified and will refresh the data.


/*
* on DOM ready handle adding to cart action
* demo.js
*/

$(function () {

  // Add an item to the cart
  $('.add-to-cart').on('click', function() {

    //get product
    var product = $(this).prev('div').text();

    // publish event
    pubsub.publish('addToCart', product)
  })
})

/*
* The view that shows the total items on cart
* simpletrolley.js
*/

var simpleTrolley = (function($){

var total = 0,
$display = $('#total-items');

// notification when an item is added to the cart
pubsub.subscribe('addToCart', function() {

  total ++;

  // update total
  updateTotal();
})

// update total items on the view
function updateTotal() {
  $display.html(total);
}

}(jQuery));

/*
* The view that shows the items detail and quantities on cart
* completetrolley.js
*/

var completeTrolley = (function(){

  var items = {},
  $trolley = $('#trolley');

  // notification when an item is added to the cart
  pubsub.subscribe('addToCart', function (product) {

    // save info

    if (!items[product]) {
      items[product] = 1;
    } else {
      items[product] ++;
    }

    // update view
    updateItems(product);
  })

  // update items on the view
  function updateItems(product) {

    // append to the view if item is not in the list
    // otherwise only update quantity

    var li = $trolley.find('li[data-product="' + product + '"]');

    if (li.length === 0) {
      $trolley.append('<li data-product="' + product + '">' + product + ': <span class="quantity">1</span></li>');
    } else {
      li.find('.quantity').html(items[product]);
    }
  }

}());

You can run the Live Demo and also get the source code from GitHub.

I hope that it became clear the advantages of using Pub/Sub and that you now have a better understanding of it, either if you use an already implement version or you want to implement it yourself.