Fundamental Patterns: Pub / Sub

Tuesday, 27th January 2015

When you subscribe to an email newsletter — or a newspaper, for example — you expect to receive delivery of the content as soon as it is published. This is the concept behind the pub/sub pattern. It allows for modules of an application to subscribe to a specific event on a global event channel where they will be notified of any publications made against the same event.

The true backbone of any complex modular application is this global event channel in which each module communicates with the rest of the app. In the case of Backbone it is the Backbone.Events module, in the case of node it is the EventEmitter from the events module. In any case, a simple pub/sub module can simplify effective app-wide communication and can quite easily be written in minimal amounts of code.

The following module, although small, is extremely useful and versatile.

/* Make the `EventChannel` globally available. */
var EventChannel = (function() {
    /* Store Array.prototype.slice for use later. */
    var _slice = Array.prototype.slice;
    /* Setup an object to register our listeners/subscribers into. */
    var _listeners = {};

    function on(ev, fn, thisArg) {
        /* Allow a subscription to set its own scope for a handler,
         * if no `thisArg` is supplied, default to the `EventChannel`. */
        thisArg = thisArg || EventChannel;

         /* Instantiate an array for the listers of type `ev`
          * if one does not already exist. */
        _listeners[ev] = _listeners[ev] || [];
        /* Push the listener `fn` and scope into the array. */
        _listeners[ev].push({ fn : fn, thisArg : thisArg });

        /* Return a decoupling function to remove the listener
         * from the array if needed. */
        var idx = _listeners[ev].length - 1;
        return function off() {
            _listeners[ev].splice(idx, 1);
        };
    }

    function publish(ev) {
        /* Store all remaining arguments to pass
         * through to the listener. */
        var props = _slice.call(arguments, 1);

         /* If there are no listeners for `ev` bail out early. */
        if (!(ev in _listeners)) return;

        /* Iterate over listeners apply remaining arguments
         * and supplied `thisArg` to each `fn`. */
        _listeners[ev].forEach(function(listener) {
            listener.fn.apply(listener.thisArg, props);
        });
    }

    return {
        on: on,    
        publish: publish
    };
})();

With this simple setup, we can subscribe to events with the following syntax:

EventChannel.on('app:ready', function() {
    /* Run some code when this event fires. */
});

Now with the publish function on the EventChannel we can push any number of values through to our listener and they will appear as arguments within our listening function. This is extremely useful for sharing tidbits of information between modules without having a reference to the originating module.

EventChannel.publish('app:ready', { hello: 'world' }, anotherFunction, aBoolean);

If we wish to unsubscribe from an event, our on function actually returns a function we can use to clear the listener from our EventChannel. This allows us to bind to events only once or to unsubscribe at any time.

var off = EventChannel.on('fire:once', function() {
    /* Run some code when this event fires and then
     * decouple our listener. */
    off();
});

Clever and pragmatic use of pub/sub can be extremely useful in large applications where there is a requirement for modularity and separation of concerns. Being such a small thing to implement and relatively easy to get your head around, it may be worth trying to see where pub/sub could help you streamline your application.