promise vs eventEmitter vs callback

2,876 views
Skip to first unread message

Tim Caswell

unread,
Dec 10, 2009, 12:06:02 AM12/10/09
to nod...@googlegroups.com
I've noticed that there are three main types of async dispatch mechanisms used in the standard node api.  I call these promise, eventEmitter, and callback.  The first two are described in detail in the docs.  Callback is when the last argument on a function is a callback function to be called when some action is complete.

Are there any best-practices yet as to when to use these three techniques and are there others I'm missing?

Here are some api's I'm thinking about using for my low level libraries.  Again, I'm looking for question and/or comments.
This section of code is modeled somewhat after ruby's DataObjects (the base library under DataMapper), but with async mixed in.

// Common setup
connection = new Persistance.Connection("sqlite3://test.db");
insert_command = connection.create_command("INSERT INTO users (login) VALUES (?)");
select_command = connection.create_command("SELECT * FROM users " +
  "WHERE username = :username AND password_hash = :password_hash");
data = {username: "admin", password_hash: "7992465911aa73783c3a5fd427637306"}

// Callback style
// Pros: Super simple, easy to implement, and easy on the machine.
// Cons: perhaps too simple, can't even emit error events has to resort to exceptions.
insert_command.execute_non_query('dbussink', sys.p);
select_command.execute_reader(data, sys.p);

// Promise style
// Pros: Still simple, can handle errors
// Cons: Adds some complication that might not be needed.
insert_command.execute_non_query('dbussink').addCallback(sys.p).addErrback(sys.debug);
select_command.execute_reader(data, sys.p).addCallback(sys.p).addErrback(sys.debug);

// EventEmitter style
// Pros: events can be tied to the command object itself. 
// Cons: a little more complicated.
insert_command.execute_non_query('dbussink')
insert_command.addListener('done', sys.p);
insert_command.addListener('error', sys.debug);
select_command.execute_reader(data, sys.p)
insert_command.addListener('row', sys.p);
insert_command.addListener('error', sys.debug);

Andrew Lunny

unread,
Dec 10, 2009, 4:56:54 AM12/10/09
to nod...@googlegroups.com
There isn't really much difference in terms of implementation - in
each case you're creating an eventEmitter, doing some asynchronous
activity, and then emitting an event. The callback mechanism is just
syntatic sugar over a promise.

I would lean towards using a promise for those db calls, unless you'd
want to return the rows from a select as a multipart stream. In terms
of api design, you can use an optional parameter to allow for all
three patterns, with a slight modification:

function foo(x,opt) {
var bar = new process.Promise();
if (opt && opt.success) bar.addListener("success",opt.success);
someAsyncFunction(x, function() {
bar.emitSuccess();
});
return bar;
}

These three calls would then be equivalent:
foo(12,{ "success": function() { sys.puts("hello"); });
foo(12).addCallback(function() { sys.puts("hello"); });
foo(12).addListener("success", function() { sys.puts("hello"); });

and "hello" would be output once someAsyncFunction executed.

Cheers,
Andrew
> --
>
> You received this message because you are subscribed to the Google Groups
> "nodejs" group.
> To post to this group, send email to nod...@googlegroups.com.
> To unsubscribe from this group, send email to
> nodejs+un...@googlegroups.com.
> For more options, visit this group at
> http://groups.google.com/group/nodejs?hl=en.
>



--
Andrew Lunny
Software Developer, Nitobi
604 685 9287
andrew...@nitobi.com

Joe Bowman

unread,
Dec 10, 2009, 8:09:29 AM12/10/09
to nodejs
Thanks Andrew, I've been trying to wrap my head around the
eventemitter and promise functionality, and I think that block just
helped me get it.

I may be do something similar in the jsondra client next time I get a
chance to work on it.
> andrew.lu...@nitobi.com

Dan Webb

unread,
Dec 10, 2009, 8:40:30 AM12/10/09
to nod...@googlegroups.com
My take so far is something like this:

EventEmitter: Use this if your object emits lots of types of events.
Promise: Use a promise if you want to emit success or failure.
Promise also gives you the wait method which is quite useful for
testing.
Callback: Use a callback if you just want to execute some code at a
certain time and you don't need to emit success or failure.

The first version of restler I did I just used callbacks for
simplicity but then I refactored so that the methods returned an
object that emitted lots of different events like 'complete',
'success', 'error', '4xx', '201' and so on. My couch DB library uses
promises so that every call can have a success and an error handler
attached to it and I could use wait() in the tests.

Saying this, Im new to the game as well so take this all with a pinch
of salt. I'm really interested to hear everyones advise on this as
well.

Cheers,

Dan


On Thu, Dec 10, 2009 at 1:09 PM, Joe Bowman <bowman...@gmail.com> wrote:
> Thanks Andrew, I've been trying to wrap my head around the
> eventemitter and promise functionality, and I think that block just
> helped me get it.


--
Dan Webb
http://massiverobot.co.uk
http://www.danwebb.net

aim: danwrong123
twitter/skype: danwrong

Eric Florenzano

unread,
Dec 10, 2009, 3:31:21 PM12/10/09
to nodejs
On Dec 9, 9:06 pm, Tim Caswell <t...@creationix.com> wrote:
> Are there any best-practices yet as to when to use these three techniques and are there others I'm missing?

I'm not sure about best practices, but in my experiments with node so
far, I've taken a hybrid approach where I accept optional function
callbacks/errbacks and then return a promise with those functions pre-
attached. [1] That way, you still get all the benefits of having a
promise that you can add extra callbacks and errbacks to, but you
still get the simplicity and straightforwardness of the callback-style
API.

[1] http://github.com/ericflo/node-jsonrpc/blob/master/src/jsonrpc.js#L13

Ryan Dahl

unread,
Dec 12, 2009, 8:43:34 AM12/12/09
to nod...@googlegroups.com
On Thu, Dec 10, 2009 at 6:06 AM, Tim Caswell <t...@creationix.com> wrote:
> I've noticed that there are three main types of async dispatch mechanisms
> used in the standard node api.  I call these promise, eventEmitter, and
> callback.  The first two are described in detail in the docs.  Callback is
> when the last argument on a function is a callback function to be called
> when some action is complete.
> Are there any best-practices yet as to when to use these three techniques
> and are there others I'm missing?

Promises are a good abstraction of an asynchronous command which will
complete at some point. This is not appropriate for everything, a TCP
server receiving a message is not an command issued by the programmer
which complete. Sometimes things just happen - a button is clicked, a
server receives a message - this is what EventEmitter is good for.

Using a callback in a function is the poor man's version of Promises.
You can't easily chain extra callbacks. However, it's cheaper, it
doesn't require creating the extra promise objects and callback chain.
Outward facing APIs should provide promises rather than callbacks.
Internally the functions implemented with c++ just use a callback.

> Here are some api's I'm thinking about using for my low level libraries.
>  Again, I'm looking for question and/or comments.
> This section of code is modeled somewhat after ruby's DataObjects (the base
> library under DataMapper), but with async mixed in.
>
> // Common setup
> connection = new Persistance.Connection("sqlite3://test.db");
> insert_command = connection.create_command("INSERT INTO users (login) VALUES
> (?)");
> select_command = connection.create_command("SELECT * FROM users " +
> "WHERE username = :username AND password_hash = :password_hash");
> data = {username: "admin", password_hash:
> "7992465911aa73783c3a5fd427637306"}

These should be promises. They are commands which will complete.

Tim Caswell

unread,
Dec 12, 2009, 9:34:43 AM12/12/09
to nod...@googlegroups.com
Thanks for confirming what I've discovered through experimentation. I had initially used promises for everything, but had problems with my connection object. I realized that regular event emitters work better there. It only has two events, but they can be fired more than once, and promise doesn't allow this or it meant for this.
Reply all
Reply to author
Forward
0 new messages