HiveBrain v1.2.0
Get Started
← Back to all entries
patternjavascriptMinor

Pub/Sub in JavaScript

Submitted by: @import:stackexchange-codereview··
0
Viewed 0 times
javascriptsubpub

Problem

This seems to work, but for some reason I'm not entirely sure about it.

Can anyone point out any issues with this simple pub/sub library?
Thank you!

(function (undefined) {
    window.mylib = $.extend(window.mylib, (function () {

        var subscriptionsBag = function() {
            var self = this;
            self.count = -1;
            self.handlers = {};

            var cancel = function(index) {
                var self = this;
                delete self.handlers[index];
            };

            self.add = function (fn) {
                if (typeof(fn) !== "function") {
                    throw new Error("function expected.");
                }

                var index = ++self.count;
                self.handlers[self.count] = fn;

                return {
                    cancel: cancel.bind(self, index)
                };
            };

            self.fire = function(obj) {
                for (var handlerKey in self.handlers) {
                    if (!self.handlers.hasOwnProperty(handlerKey)) {
                        continue;
                    }
                    var handler = self.handlers[handlerKey];
                    if (!handler) continue;
                    handler(obj);
                }
            };
        };

        var subscriptions = {
            "": {}
        };

        var publish = function(e, obj) {
            var evt = "@" + e;
            var subs = subscriptions[evt];
            if (!subs) return;

            subs.fire(obj);
        };

        var subscribeTo = function (e, fn) {
            var evt = "@" + e;
            var subs = subscriptions[evt] = (subscriptions[evt] || new subscriptionsBag());
            return subs.add(fn);
        };

        var events = {
            publish: publish,
            subscribeTo: subscribeTo
        };

        return {
            events: events
        };
    })());
})();

Solution

Member names

The code is confusing because it involves neither event dispatch nor event handling - therefore is nothing to do with events - yet there are member names evt and events, where :

  • the evt vars are actually uids used as object keys



  • the events object contains references to methods.



The loose-coupled nature of Pub/Sub does indeed tempt you into believing that it involves events. But no, it does not (at least typically not, and certainly not here). Whereas a Pub and its corresponding Subs can be in completely diffferent parts of the code base, when a Pub fires it informs the subscribers synchronously, in the same event thread.

Similarly, the subscriptions object is not well named. It receives objects which can themselves be subscribed to, not subscriptions directly. If anything was to be named "subscriptions" it would be SubscriptionsBag's self.handlers - each handler is effectively a subscription.

Leveraging jQuery.Callbacks

You are already using jQuery - great!

The jQuery documentation includes a really neat example under jQuery.Callbacks(), showing how Callbacks() instances can be exploited for Pub/Sub.

If you were to adopt this approach, it would :

  • take all the pain out of having to write/test your own .add(), .cancel() and fire() methods



  • replace your SubscriptionsBag() with a Topic() constructor



  • replace your subscriptions repository with a topics repository



  • give you nice monads that will method-chain.



You need do little more than adopt jQuery's code and assign the Topic constructor to your chosen namespace in lieu of jQuery, and trap the topics repository in a closure (or otherwise avoid the global namespace).

(function (lib, topics) {
    lib.Topic = function Topic (id) {
        var callbacks,
            topic = id && topics[id];
        if (!topic) {
            callbacks = jQuery.Callbacks();
            topic = {
                publish: callbacks.fire,
                subscribe: callbacks.add,
                unsubscribe: callbacks.remove
            };
            if (id) {
                topics[id] = topic;
            }
        }
        return topic;
    };
})(window.mylib, {});


Thus, your versions of the usage examples in the jQuery documentation would be :

// Subscribers
window.mylib.Topic( "mailArrived" ).subscribe( fn1 );
window.mylib.Topic( "mailArrived" ).subscribe( fn2 );
window.mylib.Topic( "mailSent" ).subscribe( fn1 );

// Publishers
window.mylib.Topic( "mailArrived" ).publish( "hello world!" );
window.mylib.Topic( "mailSent" ).publish( "woo! mail!" );


And an example involving asynchronously derived data :

// Subscriber
var mailArrivedTopic = window.mylib.Topic("mailArrived").subscribe(fn1); // Note how the subscribe() method returns the Topic instance (monadic behaviour).

// Publisher
$.ajax({...}).then(mailArrivedTopic.publish);

Code Snippets

(function (lib, topics) {
    lib.Topic = function Topic (id) {
        var callbacks,
            topic = id && topics[id];
        if (!topic) {
            callbacks = jQuery.Callbacks();
            topic = {
                publish: callbacks.fire,
                subscribe: callbacks.add,
                unsubscribe: callbacks.remove
            };
            if (id) {
                topics[id] = topic;
            }
        }
        return topic;
    };
})(window.mylib, {});
// Subscribers
window.mylib.Topic( "mailArrived" ).subscribe( fn1 );
window.mylib.Topic( "mailArrived" ).subscribe( fn2 );
window.mylib.Topic( "mailSent" ).subscribe( fn1 );

// Publishers
window.mylib.Topic( "mailArrived" ).publish( "hello world!" );
window.mylib.Topic( "mailSent" ).publish( "woo! mail!" );
// Subscriber
var mailArrivedTopic = window.mylib.Topic("mailArrived").subscribe(fn1); // Note how the subscribe() method returns the Topic instance (monadic behaviour).

// Publisher
$.ajax({...}).then(mailArrivedTopic.publish);

Context

StackExchange Code Review Q#111567, answer score: 4

Revisions (0)

No revisions yet.