A friend of mine recently asked me about the publish/subscribe ("pubsub") programming pattern. As this is something I use in almost every project, I thought I'd be able to find a decent tutorial for him online. Something that would be helpful to someone familiar with programming, but not familiar with this pattern. As it happens, most of the pubsub documentation or tutorials out there are specific to their use in one situation: APIs. That's all fine and dandy, but the pubsub pattern is so much more powerful and applicable in so many more situations than that niche. I am pretty sure it's my favorite programming pattern of them all. Not so hard to believe once you know that I write JavaScript for a living. But, I think JavaScript programmers are more sensitive to the applicability of the pubsub pattern than programmers in other languages, because JavaScript is asynchronous.

So what exactly does that mean?

If you've ever written any JavaScript, you have probably gotten yourself in trouble with its asynchronous nature. For example, you've probably thought about doing something like this:

// read data from a file ...
var data = readDataFromFile('/path/to/file.txt');
// ... then do something with the data
console.log('your name is: ' + data.username);

But you can't do that. JavaScript's asynchronous nature means the (totally made-up) function readDataFromFile('/path/to/file.txt') is not executed immediately. Instead it is tossed onto the event queue and executed later, after the console.log('your name is: ' + data.username); is executed. This happens because all the code in the current scope is executed in a batch, and then JavaScript grabs the next thing on the event queue and executes everything in that function's scope, and so on. "External" function calls get put onto the event queue as they are encountered, instead of executed, as they are in synchronous languages like Python or C. You could write a lot of JavaScript before you encounter this behavior, though. JavaScript is pretty nebulous about when functions get put on the event queue versus executed directly. Usually you trigger the event queue when making calls out to external resources, for example via AJAX or file I/O. Well, in order to work around this limitation in JavaScript, we use callbacks. Callbacks are functions that we pass as parameters to other functions that get executed after all the processing inside the first function is done. That sounds stupid, so let me illustrate. Here's an example of proper asynchronous JavaScript using callbacks:

// read data from a file ...
fs.readFile('/path/to/file.txt', function( err, data ) {
    //                           ^^^^^^^^^^^^^^^^^^^^^
	// ... then do something with the data *in a callback*
	if (err) {
    	throw new Error('problem doing stuff: ' + err);
    }
    console.log('your name is: ' + data.username);
});

Now you're probably asking yourself why I'm even talking about asynchronous programming in JavaScript. What does that have to do with the pubsub pattern? Well, after a while, you fall into what's called "callback hell" when you chain callbacks with callbacks with callbacks. Take a look at what should be a simple operation: Reading from a file, making a change to the data, then writing those changes to a file:

var filename = '/path/to/file.txt';
var savefile = '/path/to/new/file.txt';

fs.stat(filename, function(err, stat) {
	// inside first callback
	if (err) throw new Error('could not stat file: '+err);
    if (!stat.isFile()) throw new Error('path is not a file!');
    
    fs.readFile(filename, function(err, data) {
    	// inside second callback
    	if (err) throw new Error('could not read file: '+err);
        
        modifyUsername(data.username, function(name) {
        	// inside third callback
            
        	fs.stat(savefile, function(err, stat) {
            	// inside fourth callback
            	if (err) throw new Error('could not stat file: '+err);
                if (!stat.isFile()) throw new Error('path is not a file!');
                
                fs.writeFile(savefile, name, function(err) {
                	// i want to kill myself ..............
                    // and look at all the close brackets and parentheses!
                    // how embarrassing!
                });
            });
        });
    });
});
// yikes. are you sure you closed up all your functions properly?

"Holy shit. JavaScript sucks!" they will say. And so. Even the most faithful will pause to think.

Now, if only there were a way to write a blob of callback code, and then create a "trigger" that we could pull when we were ready to execute the callback blob, we could clean this mess right up. ... Well, that's right! You've figured out that my beloved pubsub can swoop in and save the day.

Here is what super-simplified pubsub calls look like:

// when you have a blob of code you want to run later:
subscribe(  "this-can-be-any-string",    functionToCall    );
// then when you're ready to execute the blob of code:
publish(  "this-can-be-any-string",   [  array, of, parameters  ]);

The first parameter to the subscribe() function is a string that we use to "index" the function we want to call. We store the function using this string as a label. This is so that later, when we call the publish() function, we reference the function we want to execute using the same string ("this-can-be-any-string") and then the second parameter passed to the publish() function is an array of arguments to pass to the function we will execute. In the above two lines, the result of the code ends up logically looking like this:

functionToCall(array, of, parameters);

Take a look at the pubsub object. It's very easy to read:

// here is our pubsub object
// this is how we enabled the pattern
var $pubsub = (function() {
	var cache = {};
    function _flush() { cache = {}; }
	function _pub( topic, args, scope ) {
    	if (cache[topic]) {
        	var current = cache[topic];
            for (var i=0; i<current.length; i++) {
				current[i].apply(scope || this, args || []);
            }
        }
    }
    function _sub( topic, callback ) {
    	if (!cache[topic]) {
        	cache[topic] = [];
		}
        cache[topic].push(callback);
    }
	return {
    	flush: _flush,
    	pub: _pub,
    	sub: _sub
    };
})();

Now take a look at the refactored code, which uses pubsub to escape callback hell by "subscribing" some functions to events which we later "publish":

// convenience function to DRY up our calls to fs.stat()
var fileStat = function( filename, callback ) {
	fs.stat(filename, function(err, stat) {
    	if (err) throw new Error('could not stat file: '+err);
        if (!stat.isFile()) throw new Error('path is not a file!');
        typeof(callback) === 'function' && callback();
    });
};
// now begins our list of discrete functions to execute in a specific order
// (note the $pubsub.pub() calls within each function!)
var fileRead = function( filename ) {
	fileStat(filename, function() {
        fs.readFile(filename, function(err, data) {
        	if (err) throw new Error('could not read file: '+err);
            $pubsub.pub('/username/modify', [data]);
        });
    });
};
var fileWrite = function( filename, data ) {
	fileStat(filename, function() {
        fs.writeFile(filename, data, function(err) {
        	if (err) throw new Error('could not write file: '+err);
            $pubsub.pub('/continue/process');
        });
    });
};
var modifyUsername = function( username ) {
	var newUsername = username + '_modified';
	$pubsub.pub('/file/write', ['/path/to/new/file.txt', newUsername]);
};
var moreStuff = function() {
	// do more stuff after everything else
};

// set up our subscriptions
$pubsub.sub('/file/read', fileRead);
$pubsub.sub('/file/write', fileWrite);
$pubsub.sub('/username/modify', modifyUsername);
$pubsub.sub('/continue/process', moreStuff);

// kick off the whole process with this "publish" statement
$pubsub.pub('/file/read', ['/path/to/file.txt']);

This looks a lot nicer, doesn't it? It's a bit more typing, but once you grok what's happening here in this post, you will never want to go back to that awful callback hell. So I've spent the last few minutes answering the 5 Ws of the publish/subscribe pattern by showing you how to do it. If you're not totally clear on what's happening here, start reading from the top, and hand-copy the code into your IDE. Taking a closer look by hand-copying the code is something that always helps things sink in. Before you know it, you'll grok the pubsub pattern and be using it to hit all kinds of nails.