Pub/Sub JavaScript Object
There are three keys to effective AJAX-driven websites: event delegation, History management, and effective app-wide communication with pub/sub. This blog employs of all of these techniques, and I thought I'd share the simplest of them: a tiny pub/sub module I use on this site.
If you've not used pub/sub before, the gist is that you publish to a topic and anyone can subscribe, much like the way a radio works: a radio station broadcasts (publishes) and anyone can listen (subscribes). This is excellent for highly modular web applications; it's a license to globally communicate without attaching to any specific object.
The JavaScript
The module itself is super tiny but massively useful:
var events = (function(){ var topics = {}; var hOP = topics.hasOwnProperty; return { subscribe: function(topic, listener) { // Create the topic's object if not yet created if(!hOP.call(topics, topic)) topics[topic] = []; // Add the listener to queue var index = topics[topic].push(listener) -1; // Provide handle back for removal of topic return { remove: function() { delete topics[topic][index]; } }; }, publish: function(topic, info) { // If the topic doesn't exist, or there's no listeners in queue, just leave if(!hOP.call(topics, topic)) return; // Cycle through topics queue, fire! topics[topic].forEach(function(item) { item(info != undefined ? info : {}); }); } }; })();
Publishing to a topic:
events.publish('/page/load', { url: '/some/url/path' // any argument });
...and subscribing to said topic in order to be notified of events:
var subscription = events.subscribe('/page/load', function(obj) { // Do something now that the event has occurred }); // ...sometime later where I no longer want subscription... subscription.remove();
I use pub/sub religiously on this website and this object has done me a world of good. I have one topic that fires upon each AJAX page load, and several subscriptions fire during that event (ad re-rendering, comment re-rendering, social button population, etc.). Evaluate your application and see where you might be able to use pub/sub!
How are you managing your queue? I see the “remove” method, but not how it’s used. Is the page lifetime short enough that you’re just not bothering with any kind of lifespan or garbage collection?
Good question Erryn — I’ve updated the code sample to show the removal cycle.
Nice and tiny :-)
Is this similar to signals then? I use signalsjs a lot but never bothered to look inside.
Shouldn’t the remove handle look like this?
https://gist.github.com/phusick/3eec19b56cb7c8e82ce4
This way it works as expected, i.e. it only removes a single subscription, not all the subscriptions of the topic, but the array is constantly growing as delete only sets array item to undefined and doesn’t affect array length.
Good point — I updated my post with a forEach as well!
You should check Keeto’s Company, it´s a great MooTools pub/sub I’ve been using for quite some time.. pub/sub truely shapes your code and logic in a much nicer and context agnostic way…
How about:
splice would change indices of the array and therefore remove() wouldn’t work.
sorry, my mistake
presence of undefined elements in array confuses me a little
And that’s good confusion, right thinking most likely )
Yep, but you don’t need indexes, you can search CB and remove by actual index. Otherwise it will lead to huge arrays, you don’t really delete element.
Good one. Couple of minor improvements,
1. For better readability, instead of naming the array ‘queue’, I would name it ‘listeners’.
2. In the comment, you had mentioned “Provide handle back for removal of topic “. I think it should have been “Provide handle back for removal of a listener for a topic”. You are actually removing a listener from the array not a topic.
One note of warning, something that spans ALL patterns is proper utilization. I’ve seen a number of scripts where observer patterns were utilized and somewhere along the line good practice went out the window.
It becomes REALLY easy to just start pub’ing and sub’ing all the things and suddenly you have subscriptions responding to things they were never intended to.
Using good event naming conventions or implementing something like PostalJS (https://github.com/postaljs/postal.js) are great for preventing unwieldy event subscriptions.
I agree, I’m working on a project that’s a proper example of pub/sub gone bad. When your subscribers are publishing their own things in response to other publications, it’s easy to lose control over the flow of the program and you have to work extra hard to avoid circularity.
nice little snippet **but** :P
1. the closure with the return is very pointless … you have already variables defined in the outer closure per each invoke, why that wrap? just misleading/confusing
2. this is a very good case for either an
Object.create(null)
or the usage ofhasOwnProperty
and actually ashasOwnProperty.call(topics, topic)
instead oftopics.hasOwnProperty(topic)
otherwise you have a very weak pub/sub logic3. using queue property … not sure why is that useful, but having a sparse Array is not the best thing on a long run … remember when index is 2^32 it flips back, I’d rather use
indexOf
to never set same listener twice and drop it at the right indexHi Andrea,
sorry, I accidentally submitted my last comment before I was done writing :)
My Question is: why exactly do we have to use
hOP.call(topics, topic)
instead of just invokinghOP(topic)
? Isn’t hOP already bound to thetopics
Object? Which detail am I missing here?Thanks,
Emanuel
Hi David,
I’ve been using pubsub for a long time too, its really helped me to build large JS applications in the past. I think I’m now running into limitations with the pattern now and was hoping you may have solved this issue.
I’ve found pubsub to be excellent for apps with data that flows in one direction, so with an AJAX app like you mentioned above, where various bits of the page are updated once every minute from an AJAX request to the server.
But now I’m trying to build an application that allows you to create, edit and update existing content in an admin application. This requires me to request data and respond to that data, passing the data back and forth between different modules in the app.
This has resulted in me writing what I can only call “pubsub tennis”, here’s an example:
If you take this example and chain a couple of modules into it, all doing a publish in a subscribe, you can see where I’m going with this.
Its horrible, I’ve removed some of this complexity by building each module in backbone.js (so messaging within the module itself doesn’t use the global pubsub event system) but I still need to play pubsub tennis when communicating between each module – so I essentially haven’t solved the issue.
Do you have any advice you can give me? Have you ran into pubsub tennis before?
Thanks,
Tom
David, your publish can’t pass
0
as data to subscriptions. I found this out when selecting row0
in a list.What we really want to know if is anything was sent or not. Also, for your consideration, what do you think about about a
subscribeOnce
method?I use
subscribeOnce
sometimes for convenience.What is the code doing? Please explain so others can understand. Can understand whats pub and sub. But I am unable to understand that the what code is doing and what are its objectives. Can you please explain in bit more clarity so it is useful for every one?
thanks/
Great article as always, David. One thing. Pretty sure this:
should be this:
Derp, my bad! Updated!
What about checking for deleted listeners?
Shouldn’t this:
be changed to this:
Interesting article & discussion. FWIW, I note that PubSubJS (https://github.com/mroderick/PubSubJS) is strongly of the view that broadcasts should be async, i.e. each pub is put on the end of the scheduler queue with
setTimeout(fn, 0)
.Also, consider executing the pubs from a copy of the subscriber list to avoid any unexpected issues with (un)subscribe operations which might occur during publishing.
Pubsub is a very messy pattern, you’ll regret it in any serious app.
Nice. With modern browsers it would be the same to use the following code?:
Or what would be the difference?
Source: https://developer.mozilla.org/en-US/docs/Web/Guide/Events/Creating_and_triggering_events
Why do you remove array element like that (instead splice)? It’s coold that you don’t search calback, but this will lead to huge arrays, nope?
BTW, that’s the difference between this “pub/sub” implementation and “Mediator” pattern?
Here is my mediator pattern implementation:
https://github.com/rantiev/ra-patterns/blob/master/Mediator.js
Oldie but goodie, was just about to use dojo pub/sub, but we will be discontinuing use of dojo in the future so a pure JS approach is nice. Ashamed I didn’t build it myself ;)
Hey, complete stranger passing through from the interwebz: thanks a bunch for posting this. It’s elegant, clean and useful. I was literally going to roll my own and this saved me.
10,000 opinions in the comment stream. SMH