The obvious answer to the question “should you choose an Event Emitter, Pub Sub or Deferred / Promises” is that it depends on what you are doing.
In this post I will explore a little about how each pattern works with (very) basic implementations, and then I’ll look at the reasons why you might choose one over another.
Event Emitter
What I’ve termed “Event Emitter”, a phrase borrowed from node, is probably the most well known of the three observer patterns I’m covering here in the web development world, since it’s the same pattern that DOM Events follow: you have an instance of a thing, and you bind event listener functions to it. When that event is triggered, your functions are called with some arguments.
jQuery makes this trivial:
$('.some-class').click(function(ev) {
console.log('you clicked!');
});
To implement this yourself on your own objects is also fairly easy if you only want something simplistic. I’m going to supply a simple way to do something “on()” and event, and also to “trigger()” events. Note that I haven’t written anything here to allow for “unsubscribing”, “cancellation”, “bubbling”, or provided any other utility methods like “once()”.
function EventEmitter() {
var events = {};
this.on = function(name, fn) {
events[name] = events[name] || [];
events[name].push(fn);
}
this.trigger = function(name, args) {
events[name] = events[name] || [];
args = args || [];
events[name].forEach(function(fn) {
fn.apply(this, args);
}
}
}
var my_event_emitter = new EventEmitter();
my_event_emitter.on('curtain_dropped', function(encore) {
console.log('applause');
if ( encore ) console.log('encore! encore!');
});
my_event_emitter.trigger('curtain_dropped', [true]);
Pub Sub
Pub Sub is in some ways quite similar to Event Emitter. The key difference is that you do everything in something of a “void”. You never know anything about what published a “topic” or wether anything is “subscribed” – the pieces are much more loosely joined.
Here’s a very basic implementation of the pattern:
// since we're creating global functions on window, let's wrap
// everything in a closure so that we get a private scope
(function(global) {
var topics = {};
global.subscribe = function(name, fn) {
topics[name] = topics[name] || [];
topics[name].push(fn);
}
global.publish = function(name, args) {
topics[name] = topics[name] || [];
topics[name].forEach(function(fn) {
fn.apply(this, args);
});
}
})(window);
subscribe('download_started', function(total_kb) {
console.log('will download an extra', total_kb);
});
publish('download_started', [1024]);
Deferred Promises
The mighty Kris Zyp authored the CommonJS Promises/A proposal. To describe why promises are useful, you can consider a function which takes a “callback” as an argument, such as an HTTP request. If you really only want to do one thing in your callback, that’s fine, but as code and requirements get more complicated, your callback can become very messy and full of branching code.
By using the promise pattern, you don’t provide a callback to the function. Instead, the object you get back from the function allows you add callbacks to itself – this is called the “promise” object. Let’s look at how you might code it, bearing in mind that there’s a lot here I haven’t done (such as a when() function, the ability to add done() and fail() callbacks after resolution, chaining, etc, etc).
function Deferred() {
var resolved_callbacks = [],
rejected_callbacks = [];
this.resolve = function(args) {
resolved_callbacks.forEach(function(fn) {
fn.apply(this, args);
});
};
this.reject = function(args) {
rejected_callbacks.forEach(function(fn) {
fn.apply(this, args);
});
};
this.promise = {
done : function(fn) {
resolved_callbacks.push(fn);
}
fail : function(fn) {
rejected_callbacks.push(fn);
}
}
}
// In order to use this, some async function of
// yours will create a deferred, resolve or reject
// it later, and return its promise.
function make_ajax_request(url) {
var deferred = new Deferred();
$.get(url, {
success: function(data) {
deferred.resolve(data);
},
error: function(xhr, error) {
deferred.reject(error);
}
});
return deferred.promise;
}
// now you get to the real client part,
// where we are calling the async function,
// and attaching various callbacks willy nilly.
var promise = make_ajax_request('foo.json');
promise.done(function() {
console.log('it succeeded!');
}
promise.fail(function() {
console.log('it failed');
}
if ( /\bMSIE 6/.test(navigator.userAgent) ) {
promise.done(function() {
console.log('Amazing that anything works really');
}
promise.fail(function() {
console.log('Well, what did you expect!');
}
}
[NB - the example implementation above is even more stupid than it might first appear because jQuery returns "promises" from all ajax functions!]
Discussion Part 1: EE vs PS
The two of these three patterns that are most straightforward to compare are the Event Emitter and Pub Sub. Here’s a quick breakdown:
| Event Emitter | Pub Sub | |
|---|---|---|
| Coupling | Tight | Loose |
| Control | Fine | Coarse |
| Example | Parents reacting to child events | Global interactions across UI |
As you can see, Event Emitter requires that you have a reference to the “thing” that you are listening to an event on. Pub Sub does not have that requirement, and all topics are published in a single “global” scope.
Pub Sub is more simple to manage in many ways – something publishes, something subscribes, and that’s all there is to it. However, it breaks the idea of encapsulation really thoroughly. Event Emitter on the other hand has a higher barrier to entry – you have to “know” about the thing you want to listen to events on, but it does keep things much more contained with known boundaries.
The question then is, what are you building? If you’re sticking very strictly to something like Nikolas Zakas’ sandboxed model (which seems to be a very nice fit for the Yahoo homepage, but not every kind of web page) then you can stick to Event Emitter and you don’t need to worry about much else. If you’re building a complex, rich, interconnected UI you’re almost certainly going to get some benefit from using Pub Sub.
I would also suggest that a lot of things that happen “globally” could be done very nicely with Pub Sub. Take logging for example – whether you’re using console.log or other global logging method, it might make much more sense to publish on a logging topic – and allow anything that wants to subscribe to it, whether that’s a user notification system, or for debugging, or whatever. Another example might be network activity – by publishing individual network events globally, it becomes much easier to manage user notification (for example with a single compound spinner / loading bar).
Discussion Part 2: Deferred Promises
Using deferred and promises was a bit more of a mental leap for me to start with. In fact, it wasn’t until I messed about with trying to implement the Promises/A specification myself that I really started to get the idea and grok how to use it.
Unless you’re writing some kind of framework, you’re fairly unlikely to be creating a deferred object and returning a promise. It’s more likely that you’ll be using a promise given back to you by something else, for example jQuery’s AJAX functions. Promises are really just a more powerful way of doing “callbacks”, so definitely have their uses. We can look at some jQuery code here:
// STAGE 1 - SIMPLE
// callbacks
$.get('foo.json', {
success: function(data) {
updateDom(data);
},
error: function() {
alert('there was a problem');
}
);
// same thing, using the promise:
$.get('foo')
.done(function(data) {
updateDom(data);
})
.fail(function() {
alert('there was a problem');
});
// STAGE 2
// now lets do something else on success AND failure
// callbacks
$.get('foo.json', {
success: function(data) {
removeLoadingIndicator();
updateDom(data);
},
error: function() {
removeLoadingIndicator();
alert('there was a problem');
}
);
//promise
$.get('foo')
.done(function(data) {
updateDom(data);
})
.fail(function() {
alert('there was a problem');
});
.always(function() {
removeLoadingIndicator();
});
// STAGE 3
// let's branch depending on some states
// callbacks
$.get('foo.json', {
success: function(data) {
updateDom(data);
if ( !waitingForMore() ) {
removeLoadingIndicator();
} else {
updateLoadingIndicator();
}
},
error: function() {
alert('there was a problem');
if ( !waitingForMore() ) {
removeLoadingIndicator();
} else {
updateLoadingIndicator();
}
}
);
// and promises
//promise
$.get('foo')
.done(function(data) {
updateDom(data);
})
.fail(function() {
alert('there was a problem');
});
.always(function() {
if ( !waitingForMore() ) {
removeLoadingIndicator();
} else {
updateLoadingIndicator();
}
});
As you can see, the more complex the things you want to do with your callbacks, the nicer promises seem. You normally get a fairly nice API with Deffered / Promises implementations, which will let you add “callbacks” even after the async operation has completed – and they will just be executed immediately. If you find CommonJS a bit hard to plough through, you could take a look at the jQuery Deferred Object.
Summary
So we looked at three variations of the basic “Observer” pattern, how they might be implemented and what they might be useful for: Event Emitter is a real classic, and allows for good practices and control over things that happen; Pub Sub is more flexible for cross-component events; Deferred and Promises give a powerful way to handle callbacks.
Miller Medeiros
I also wrote a comparison between different Observer implementations listing pros/cons of each: https://github.com/millermedeiros/js-signals/wiki/Comparison-between-different-Observer-Pattern-implementations – I would pick a Signal over EventEmitter and pub/sub on most cases.
Promises are very useful when you need callbacks for “error” and “success” and/or when you have async events that should happen only once and listeners might be added after the action already executed. – Most event systems doesn’t keep a track of previously dispatched messages so the order that you attach the listener and dispatch the event is very important (js-signals has a “memorize” option for that).
Promises are usually recommended when you trigger the action (eg. call a method that does something asynchronous) while Events are for cases where you don’t necessarily trigger the action. In many cases I end up using Promises for “internal” code (inside same module) and dispatching Signals for cross-module communication, that way multiple modules can listen to the event without triggering the action.
There is also an interesting discussing on JSMentors about pub/sub and Signals: https://groups.google.com/forum/?hl=en&fromgroups=#!topic/jsmentors/aW_O7bhbgfI (see my conversation with Scott Sauyet)
Cheers.
Eric Elliott
A couple comments:
Pubsub doesn’t break encapsulation at all. It preserves it. All your modules must know that the pubsub methods exist, but by coupling with the pubsub api, they can effectively decouple from everything else. Need to keep your data immutable to the outside world? Send messages by copy instead Of by reference.
What does thoroughly break encapsulation is eventemitter, because it forces tight coupling between emitter and listener.
You shouldn’t choose one over the other. Both have an important place in a large application.
I’m a little confused about why you lumped deferreds into this discussion. Could you use them as a message passing API? Sure. But they’re a specialized version that does one thing: notifies you one time about the status of a single asyncronous operation (or single collection of async operations with .when()). It does not solve the same class of problems solved by pub sub or event emitters.
I just finished covering this topic for my book, “Programming JavaScript Applications” (O’Reilly). It will be released as an update to the early edition soon. Check it out.
pete
Thanks for the comments.
Miller – I love JSSignals, and wanted to write another post about it.
Eric – I guess we have a different idea of what “encapsulation” means. I always think that if each “thing” is only taking managing it’s own children (i.e. the other things it constructs) then you have achieved a high level of encapsulation. There is a distinct danger that by using PubSub you cn introduce subtle dependencies between cousins or other even less closely related objects.
In general – I just wanted to put up some notes about the implementations, and to take a look at each one. I probably chose a poor title for the post.
Andreas
Thanks for a nice introduction to promises! Great to get up to speed on this. Right now I’m using Iced Coffeescript but I can see the need for promises soon. I’ll link to your post when it’s time to write about that!