patternjavascriptMinor
Pub/Sub in JavaScript
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!
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
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
Leveraging jQuery.Callbacks
You are already using jQuery - great!
The jQuery documentation includes a really neat example under jQuery.Callbacks(), showing how
If you were to adopt this approach, it would :
You need do little more than adopt jQuery's code and assign the
Thus, your versions of the usage examples in the jQuery documentation would be :
And an example involving asynchronously derived data :
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
evtvars are actually uids used as object keys
- the
eventsobject 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()andfire()methods
- replace your
SubscriptionsBag()with aTopic()constructor
- replace your
subscriptionsrepository with atopicsrepository
- 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.