Publisher / Subscriber Pattern

This Lesson teaches the Publisher / Subscriber pattern, which is used to allow modules to communicate with eachother without hard dependencies. It is a great pattern for decoupling modules in your program

  • It is very common to use this pattern in JS applications
  • this pattern can easily be overused as not all coupling between modules is bad
  • DO NOT use pubSub to the point where no modules depend on eachother in your application, this can be confusing and lead to code bloat

 
First Step is to set up PubSub module:

// JavaScript Document - PubSub module will return the methods publish and subscribe and we will need an object to store registered callbacks (subscribers)
define(function () {
'use strict';

var subscribers = {};

return {
publish: function (topic, data) {//this function receives the topic to publish, and optionaly some data to pass to the subscribers
if (!subscribers[topic]) { //check if there are subscribers of the topic. If not, it is not worth publishing (?)
return;
}

subscribers[topic].forEach(function (subscriber) {//now iterate the array for the topic, invoking any subscribers and passing any data supplied to publish method. We use forEach method to iterate array, callback function passed to forEach method is invoked for each callback in the array, which is then invoked and passed the data (?)
subscriber(data);
});
},
subscribe: function (topic, callback) {//best to work with subscribe function first, this is method that other modules will use to register a callback with. It accepts two arguments, event name to subscribe to (topic) and callback function to invoke when topic us published.
var index; //add an index variable to subscribe method, this will allow for unsubscriptions
if (!subscribers[topic]) { //first we must check if the topic being registered already exists. This checks is subscriber.topic is falsey, and if it is, then that topic is created and pushed into an empty array
subscribers[topic] = []; //if not, it must be created as an array
}

index = subscribers[topic].push(callback) -1; //then the callback is pushed into the subscriber array. Also, the return value is then added to the index variable. Since push method returns new length of array, subracting one from it gives us the index of the callback we just added

return { //now we will return object from subscribe method that exposes the dispose method. It is common to allow subscribers to unsubscribe, and this is how we will do it
dispose: function() {
subscribers[topic].splice(index, 1); //in dispose method, we slice our the newest callback from the array of subscribers.
}
};
}
};
});

We will then set up the individual publisher and subscriber modules, called ModuleA and ModuleB for convenience


// JavaScript Document setting up subscription module
define(function (require) {
'use strict';

var pubSub = require('pubSub/pubSub'), //load pubSub module
subscription; //add new variable for subscription

subscription = pubSub.subscribe('atopic', function (data) { //this uses the subscribe method to subscribe to a topic. 2nd level populates the subscription variable with the method that is returned from subscribe method
console.log('atopic was published with the data: ' + data.something); //inside handler function, log out a message and the data argument
subscription.dispose(); //inside handler, we invoke this new method.
});

});

and


// JavaScript Document setting up Publisher Module
define(function (require) { //use require function
'use strict';

var pubSub = require('pubSub/pubSub'); //load pubSub module

return { //now we want to return a method that lets us publish to topic
publishEvent: function () {
var data = { //this creates a simple data object with a single property with...
something: 'some data' //the key of something and the value of some data
};

pubSub.publish('atopic', data); //then pubSub modules publish method is used to publish to topic called atopic and pass through our data object
}
};

});

We then set up our initialization script


define(function(require) { //now we must update the init module in our pubSub folder to require the new modules
'use strict';

return {
init: function() {

var moduleA = require('pubSub/moduleA'),
moduleB = require('pubSub/moduleB'); //loads the modules

moduleB.publishEvent();//use publish event method in moduleB to publish the event
moduleB.publishEvent();

}
};
});

and then add this initialization to our Main JavaScript file


// JavaScript Document
require (
['factory/init', 'pubSub/init'],
function (factory, pubSub) {
'use strict';

var examples = {
factory: factory,
pubSub: pubSub
};

window.runExample = function (example) {
examples[example].init();
};
}
);

With all that complete, we connect the main.js file to the bottom of the body in our index.html file so we can see our work live:


<script data-main="js/main" src="js/main.js">

Once the page loads, we can open the browser console and use

runExample('pubSub')

to see our pubSub module in action!

Summary

In this lesson, we looked at pubsub method implementation. Our module exposes two methods, publish method notifies subscribers that something has occured. This occurs over named channel (topic). Subscribe method allows modules to subscribe to notifications (events) and registers a callback that is invoked wwhen event is published. Can easily pass data into callbacks and can supply dispose method that can be used to unsubscribe from events.

Main advantage of pubSub is that it allows us to keep modules loosely coupled, but it also makes the relationships between the modules obscured, so it can be overused easily and cause confusion.