Comparison between continuables, promises and many variants on them.

1,331 views
Skip to first unread message

Tim Caswell

unread,
Feb 18, 2010, 3:56:46 PM2/18/10
to nod...@googlegroups.com
There has been a lot of discussion lately about the best way to do async functions in node.  This is so important because node has non-blocking IO everywhere and regular exceptions don't work in this case.

Several of us in IRC made a gist to show a quick example of the different styles.


This is a simple example, but it highlights enough of the differences to get a feel for how everything works.

I think that ideally node should ship with three versions of the async io libraries.

 - A base library based on pure callbacks
 - A true sync version of that library for the times you don't care about blocking
 - And a "blessed" async library that wraps the base async library in Ryan's favorite of these options.

The plain async library will great for us who want to do our own thing and don't want the overhead of wrapping someone else's stuff that you don't like.

The sync version will be very useful for short running scripts and things that only happen at server startup.

And for the node programmers who don't care what style of async code they use, but just want a common official style that's better than plain callbacks, will use the third lib.

The first two libs have to be shipped with node, but third may or may not.

Ciaran

unread,
Feb 18, 2010, 4:01:51 PM2/18/10
to nod...@googlegroups.com
On Thu, Feb 18, 2010 at 8:56 PM, Tim Caswell <t...@creationix.com> wrote:
> There has been a lot of discussion lately about the best way to do async
> functions in node.  This is so important because node has non-blocking IO
> everywhere and regular exceptions don't work in this case.
> Several of us in IRC made a gist to show a quick example of the different
> styles.

Thank-you :)
-cj.

Tim Caswell

unread,
Feb 18, 2010, 4:03:01 PM2/18/10
to nod...@googlegroups.com
Here's the source in case github ever is down again:

// test_and_load does two things. First it checks to see if a filepath
// is really a file and not a directory ot something crazy.
// If it's not a file it emits "undefined" (except for one case where null is used)
// If it is a file, then it emits the file's contents
// Any errors in fs.stat or fs.fileRead are passed through as is.
 
// In these examples, we're assuming that the fs module is of the same
// style as the function comsuming it. So we're showing a small example of
// producer and consumer for each of the styles.
 
// simple continuable with seperate callback and errback
function test_and_load(filename) { return function (callback, errback) {
  fs.stat(filename)(function (stat) {
 
    // Filter out non-files
    if (!stat.isFile()) { callback(); return; }
 
    // Otherwise read the file in
    fs.readFile(filename)(callback, errback);
 
  }, errback);
}}
 
// simple continuable with single combined callback
function test_and_load(filename) { return function (callback) {
  fs.stat(filename)(function (stat) {
 
    // Pass along any errors before we do anything
    if (stat instanceof Error) { callback(stat); return; }
 
    // Filter out non-files
    if (!stat.isFile()) { callback(); return; }
 
    // Otherwise read the file in
    fs.readFile(filename)(callback);
 
  });
}}
 
// inimino style continuables
function test_and_load(filename) { return function (cont) {
  fs.stat(filename)(rightContinuation(cont)(function (stat) {
 
    // Filter out non-files
    if (!stat.isFile()) { cont(); return; }
 
    // Otherwise read the file in
    fs.readFile(filename)(cont);
 
  }));
}}
 
 
function test_and_load(filename) {
  return fs.stat(filename)(function (stat) {
    
    // Pass along any errors before we do anything
    if(stat instanceof Error) { return; }
    
    // Filter out non-files
    if (!stat.isFile()) { return null; }
 
    // Otherwise read the file in
    return fs.readFile(filename);
 
  });
}
 
// promise based with node-promise helpers (github.com/kriszyp/node-promise)
var when = require("promise").when;
function test_and_load(filename) {
  return when(fs.stat(filename), function (stat) {
 
    // Filter out non-files
    if (!stat.isFile()) { return; }
 
    // Otherwise read the file in
    return fs.readFile(filename);
    
  });
}
 
 
// promise based with CommonJS monkey patch (github.com/kriszyp/node-commonjs/blob/master/patch-promise.js)
function test_and_load(filename) {
  return fs.stat(filename).then(function (stat) {
 
    // Filter out non-files
    if (!stat.isFile()) { return; }
 
    // Otherwise read the file in
    return fs.readFile(filename);
    
  });
}
 
 
// promise based (how it works in current node)
function test_and_load(filename) {
  var promise = new process.Promise();
  fs.stat(filename).addCallback(function (stat) {
 
    // Filter out non-files
    if (!stat.isFile()) { promise.emitSuccess(); return; }
 
    // Otherwise read the file in
    fs.readFile(filename).addCallback(function (data) {
      promise.emitSuccess(data);
    }).addErrback(function (error) {
      promise.emitError(error);
    });
 
  }).addErrback(function (error) {
    promise.emitError(error);
  });
  return promise;
}

Ryan Dahl

unread,
Feb 18, 2010, 4:10:32 PM2/18/10
to nod...@googlegroups.com
On Thu, Feb 18, 2010 at 12:56 PM, Tim Caswell <t...@creationix.com> wrote:
> There has been a lot of discussion lately about the best way to do async
> functions in node.  This is so important because node has non-blocking IO
> everywhere and regular exceptions don't work in this case.
> Several of us in IRC made a gist to show a quick example of the different
> styles.
> https://gist.github.com/602efd6a0d24b77fda36
> This is a simple example, but it highlights enough of the differences to get
> a feel for how everything works.
> I think that ideally node should ship with three versions of the async io
> libraries.
>  - A base library based on pure callbacks
>  - A true sync version of that library for the times you don't care about
> blocking

Well, for file system operations I've decided to allow sync op
exposure because of the high possibility of file system caches. For
other things which use promises (DNS resolution and simple HTTP GET
requests) there will be no synchronous interface.

I'm glad we're discussing this - I think the current promises could be improved.

Tim Caswell

unread,
Feb 18, 2010, 4:13:43 PM2/18/10
to nod...@googlegroups.com
ok, for things where the delay could be large like http.cat, I agree. There is no need for a sync version.

inimino

unread,
Feb 18, 2010, 4:19:47 PM2/18/10
to nod...@googlegroups.com
On 2010-02-18 13:56, Tim Caswell wrote:
> - A base library based on pure callbacks

+1

> - A true sync version of that library for the times you don't care about blocking

+0.5

I'd be even happier with no such library, but given the
existence of .wait(), this boat has probably sailed...

> - And a "blessed" async library that wraps the base async library in Ryan's favorite of these options.

-1

I don't think there needs to be a "blessed" library, but
we do need an installer for packages (which I know several
people are working on). Once this is in place it should be
no problem to pull in async convenience libraries for use in
your own projects or as dependencies of other libraries or
frameworks you install.

I think Promises will get a more full-featured implementation
this way then what most node users have seen of them so far,
while Node itself can stay light, and we can have continuable
libraries and other approaches as well to suit various styles
and preferences.

--
http://inimino.org/~inimino/blog/

Karl Guertin

unread,
Feb 18, 2010, 4:55:17 PM2/18/10
to nod...@googlegroups.com
On Thu, Feb 18, 2010 at 4:19 PM, inimino <ini...@inimino.org> wrote:
> I don't think there needs to be a "blessed" library, but
> we do need an installer for packages (which I know several
> people are working on).  Once this is in place it should be
> no problem to pull in async convenience libraries for use in
> your own projects or as dependencies of other libraries or
> frameworks you install.

I think there should be a reasonable "start here" for people just
getting into things. I'd also appreciate having something for the
times when I'm on a remote system and want to bang out a script
without installing stuff (this is actually one of my annoyances with
Python, I hate the stdlib for scripting jobs). Maybe the sync versions
can cover that but I'd still like some blessed option.

I actually have ideas on yet another way to do this that I'd like to
try out over the weekend, but I'd be happy enough with more or less
all of the options presented.

Tom Van Cutsem

unread,
Feb 18, 2010, 7:59:44 PM2/18/10
to nodejs
Hi node.js people,

I was informed by Kris Kowal that you guys are discussing various
options for an asynchronous API. Having worked extensively with a
promise-based API myself, Kris thought it would be useful if I would
share my thoughts on this list. So here goes:

A promise-based API has the benefit that asynchronous actions can be
'chained' implicitly and that they bundle up callback and errback in a
single abstraction. Consider the simplest form of composition: an
asynchronous action A that just wants to chain two asynchronous
actions B and C. A good measuring point to compare each async. version
against is the sequential version:
function A(x) { return B(C(x)); }

With explicit continuations, you get something resembling:
function A(x, cb, errb) { return C(x, function(val) { return B(val,
cb, errb); }, errb);

With promises you get:
function A(x) { return when(C(x), function(val) { return B(val); } }

The difference may not appear to be that big, but the key point is
that it's not a constant-factor improvement: the more async.
compositions you perform in your code, the larger the difference
between the callback and the promise-based version becomes. Another
cool thing to note with a promise-based API is that the "signature" of
your functions is unmodified as compared to the sequential API, except
that the "return type" is of type promise<T> instead of type T, so to
speak. In my experience, this reduces the burden of reasoning about
async API's. And it means a promise-based API plays nicer with
variable-argument functions, which don't have to perform argument-list-
surgery to treat continuation arguments specially.

I can think of other reasons for choosing promises, but given the
context of this discussion, these may not be that relevant here.
Just my $0.02.

Cheers,
Tom

Tim Caswell

unread,
Feb 19, 2010, 12:23:22 AM2/19/10
to nod...@googlegroups.com
Just to be clear, continuations are not plain callbacks.  They don't modify your signature either and work with multiple argument functions our of the box.  Also you can think of the return value being a continuation of type <t>.

// Plain callback style
function read(filename, callback, errback) {
  // Do async work and then...
  callback(data);
}
 
// Continuation style
function read(filename) { return function (callback, errback) {
  // Do async work and then...
  callback(data);
}}
 
 
// Using a callback
read("myfile.txt", function (data) {
  // use data
}, error_handler);
 
// Using a continuation
read("myfile.txt")(function (data) {
  // Use data
}, error_handler);

Note that this is just my modification on continuations.  There are a few variants running around, and one of them does a lot of the implicit stuff that promises do. My version doesn't require any library at all to use.  It's just a convention of currying the initial arguments and then attaching the callback later when you want to run it.  This make variable arguments possible and allows for utility libraries like Do work do the advanced stuff when needed.

If you were to write a full on continuation library then it's just a powerful as promises, just a shorter syntax in most cases.  

I'm not saying that continuations are always better or even better for everyone.  I just want us to all be talking about the same thing.

Of course promises are easier to use than plain callbacks because they allow all that nifty chaining and composition. Continuables can be just as powerful as promises depending on which version you use.  It's mainly a thing of style and taste as far as I'm concerned.

I think that easy things should be easy.  Promises don't do this, you have to include an entire promise library, create a promise in your function, return the promise and then call a method on the promise later when you've got the value. with my simple continuables, you just curry your function into two parts.  The arguments and the callbacks.  That's it, the callback is there to use when you have the value and there is nothing to return.

Benjamin Thomas

unread,
Feb 19, 2010, 1:23:36 AM2/19/10
to nod...@googlegroups.com
I think at this point it is clear that a consensus is not going to be
reached as to which approach is The Best Way. The thing to do is
decide what Node should do to make it as easy as possible for people
to make their own choices about how to do async.

I think Tim's suggestions go a long way towards making this possible. So, +1 for

Ryan Dahl

unread,
Feb 19, 2010, 4:26:59 AM2/19/10
to nod...@googlegroups.com
What about this:

// three ways of calling fs.open()

// sync
try {
fs.open('/tmp/hello', 'r+', 0600);
puts('complete');
} catch (err) {
puts('error');
}

// simple async
fs.open('/tmp/hello', 'r+', 0600, function (err, fd) {
if (err) {
puts('error');
} else {
puts('complete');
}
});

// promise async
promise(fs.open, '/tmp/hello', 'r+', 0600)(function (fd) {
puts('complete');
}, function (err) {
puts('error');
});

Then we can expose all three options.

Michael Stillwell

unread,
Feb 19, 2010, 5:36:29 AM2/19/10
to nod...@googlegroups.com
On Fri, Feb 19, 2010 at 9:26 AM, Ryan Dahl <coldre...@gmail.com> wrote:

>  // promise async
>  promise(fs.open, '/tmp/hello', 'r+', 0600)(function (fd) {
>    puts('complete');
>  }, function (err) {
>    puts('error');
>  });

I mentioned this a few days ago[1], but if you add a curry-like
function to Function.prototype you could end up with

fs.open.promise('/tmp/hello', 'r+', 0600)(function (fd) {


puts('complete');
}, function (err) {
puts('error');
});

which is then syntactically very close to continuables.

I do think it's often useful to encapsulate the "what happens next"
code paths into an object, though. Objects are not a necessary part of
any programming language, but if objects are fundamental to
JavaScript, and async programming is fundamental to JavaScript, then I
think it's useful to have them combined in some way.

If the "what happens next" stuff is an object the various code paths
are named, they can be inherited and monkey patched and inspected,
they can easily be passed around and emitted, and they can easily
support more than two code paths, as well as operations like timeout.
Many of the continuable examples I've seen are very elegant (they're
particularly nice when chaining a "success" code path), but I think
they get a bit awkward when the "error" code path is introduced (is
error a second argument, or is a single argument "overloaded"?) and
none have a good solution for timeouts.

Michael

[1] http://groups.google.com/group/nodejs/msg/b8a9b8b644bbd291

--
http://beebo.org

Michael Stillwell

unread,
Feb 19, 2010, 6:01:21 AM2/19/10
to nod...@googlegroups.com
On Fri, Feb 19, 2010 at 5:23 AM, Tim Caswell <t...@creationix.com> wrote:
> Just to be clear, continuations are not plain callbacks.  They don't modify
> your signature either and work with multiple argument functions our of the
> box.  Also you can think of the return value being a continuation of type
> <t>.

I'm a bit confused here. I thought a "continuable" was the curried
version of a function written in continuation passing style[1].

So you have

function add(i, j) {
return i + j;
}

console.log(add(1, 2));
// -> 3

function add_cps(i, j, callback) { // cps = continuation passing style
callback(add(i, j));
}

add_cps(1, 2, function(x) { console.log(x); });
// -> 3

function add_continuable(i, j) {
return function(callback) { add_cps(i, j, callback); }
}

add_continuable(1, 2)(function (x) { console.log(x); });
// -> 3

I thought "add_continuable" was the only actual "continuable" in the
code above, and that the various callback arguments are the
"continuations"--?


Michael

[1] http://en.wikipedia.org/wiki/Continuation-passing_style

--
http://beebo.org

Rasmus Andersson

unread,
Feb 19, 2010, 9:04:31 AM2/19/10
to nod...@googlegroups.com
Don't forget continuations are both hard to grasp and tricky to use
it's full potential for most users. Node (and Javascript in general)
are both easy to learn and understand. The promise model is also
straight-forward and includes no "hidden" language tricks (like the
continuables discussed do). When hiding what's going on, mistakes
appear.

I believe the current promise model (or the promise function
shorthand, as suggested by Ryan above) in combination with the
CommonJS "then" would both give us flexibility and all "papers on the
tables" (fewer language tricks causing confusion).

Rather write an extra line of code than having to explain the code to someone.

> --
> 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.
>
>

--
Rasmus Andersson

Kris Zyp

unread,
Feb 19, 2010, 9:07:52 AM2/19/10
to nodejs
To make the comparison a little clearer, it might be helpful to break
down the different aspects of what is being proposed, rather than just
arguing continuables vs promises. In some ways, continuable are a
different type of promise, with several particular differences:

1. API/signature for registering listeners. Node's promises are an
object with addCallback/addErrback for registering listeners.
CommonJS's promises are objects that use "then" to register the
callback and error handler. Continuables are the function itself that
is called to register the listeners. Interesting, Continuables are
actually closer to CommonJS promises here, since you have a single
function for registering listeners, they only differ in whether the
promise is that function itself or an object that holds the function.
Modifying continuables to use then would be pretty trivial as well you
just use "return {then:function(..." instead "return function(...".
And actually, the continuable style of promise API is similar to
ref_send's promises, which are also functions that are called
directly. We have certainly considered this before, but, the major
flaw in this design, and the reason we didn't follow this approach in
CommonJS, is that it becomes very hard for functions that need to
differentiate between sync values and async values to branch when the
only definition of a promise is a value where (typeof value ===
"function"). This type of branching is a key part of the when()
construct (and I use when() all the time), where it provides
normalization between sync and async. If you call a function that
returns a function, how do you determine if the return value is
synchronous and really a function, or is a promise/continuable that
needs a listener? Of course the other aspect of this is more
subjective, having a function call in your code, instead of just
adjacent parenthesis explicitly indicates to the reader of the code
that you are setting up a function to handle the completion of an
operation.

2. No automatic chaining. Node's promises don't support chaining
anyway, but CommonJS promises do. Without chaining, users have to
manually fulfill the continuable/promise that is returned. The gist
that started (thanks Tim for putting that together) clearly shows the
difference between chaining and no chaining.

3. Registering listeners triggers the execution the of async
operation. With normal promises, execution and listener registration
are separate concerns. One can fire off an async operation and then
safely add (or not add) listeners without affecting the operation.
This is probably the most dangerous and insidious aspect of
continuables, IMO. If multiple listeners are registered, this causes
multiple firings of the async operation. If one were to do something
like:

var myContinuable = appendToMyFile();
printResults(myContinuable);

Now if the author of printResults decided to register an extra
callback to do another debugging statement, that would cause multiple
executions of the action in appendToMyFile. In this case, where
appendToMyFile is probably not idempotent, this results in duplicate
appends to the file. Users have an enormous extra responsibility to
ensure that a continuable is only called once (which can be quite
onerous since it is so easy to pass the continuable around). This type
of behavior also prevents one from writing a safe reliable version
when() for use with continuables (whereas when() would work with #1
and #2, except for the amiguity problem).

Anyway, hopefully it is helpful for the sake of discussion (or at
least education) to be able consider each of these ideas individually.

Rasmus Andersson

unread,
Feb 19, 2010, 9:12:09 AM2/19/10
to nod...@googlegroups.com
On Fri, Feb 19, 2010 at 15:07, Kris Zyp <kri...@gmail.com> wrote:
<snip>

>
> var myContinuable = appendToMyFile();
> printResults(myContinuable);
>
> Now if the author of printResults decided to register an extra
> callback to do another debugging statement, that would cause multiple
> executions of the action in appendToMyFile. In this case, where
> appendToMyFile is probably not idempotent, this results in duplicate
> appends to the file. Users have an enormous extra responsibility to
> ensure that a continuable is only called once (which can be quite
> onerous since it is so easy to pass the continuable around). This type

Agreed. This is one of the problems with this kind of continuation I
was trying to point at.

Thanks for the break-down.

Kris Zyp

unread,
Feb 19, 2010, 9:12:39 AM2/19/10
to nodejs

On Feb 19, 2:26 am, Ryan Dahl <coldredle...@gmail.com> wrote:
> What about this:
> [snip]


>   // promise async
>   promise(fs.open, '/tmp/hello', 'r+', 0600)(function (fd) {
>     puts('complete');
>   }, function (err) {
>     puts('error');
>   });

Would node provide the promise function, or third party libraries?
Would Node have a function called "promise" that returns a continuable
(instead of a promise)? I'd prefer having third parties provide the
promise and continuable APIs over this being the third option.
Kris

Felix Geisendörfer

unread,
Feb 19, 2010, 9:19:46 AM2/19/10
to nodejs
> I'd prefer having third parties provide the
> promise and continuable APIs over this being the third option.

+1

I think node should go with returning a function that accepts a single
callback, and the first parameter is reserved for indicating if there
was an error.

All high level wrapping and mangling should be done using 3rd party
libraries.

--fg

Rasmus Andersson

unread,
Feb 19, 2010, 9:27:16 AM2/19/10
to nod...@googlegroups.com
2010/2/19 Felix Geisendörfer <fe...@debuggable.com>:

>> I'd prefer having third parties provide the
>> promise and continuable APIs over this being the third option.
>
> +1
>
> I think node should go with returning a function that accepts a single
> callback, and the first parameter is reserved for indicating if there
> was an error.

+1

This is how I usually do it in C-blocks[1] and appreciate not having
to define two separate callbacks. e.g.

typedef int (^closure_t)(error_t *, void *);
void parse_tree(closure_t c) {
// ... eventually call c
}
parse_tree(^(error_t *err, void *arg) {
if (err) {
present_error(err);
return;
}
parsed_tree_t *tree = (parsed_tree_t*)arg;
// ...
});


[1] http://thirdcog.eu/pwcblocks/

>
> All high level wrapping and mangling should be done using 3rd party
> libraries.

Indeed. Apparently there are many different tastes out there.

>
> --fg
>
>
> On Feb 19, 3:12 pm, Kris Zyp <kris...@gmail.com> wrote:
>> On Feb 19, 2:26 am, Ryan Dahl <coldredle...@gmail.com> wrote:
>>
>> > What about this:
>> > [snip]
>> >   // promise async
>> >   promise(fs.open, '/tmp/hello', 'r+', 0600)(function (fd) {
>> >     puts('complete');
>> >   }, function (err) {
>> >     puts('error');
>> >   });
>>
>> Would node provide the promise function, or third party libraries?
>> Would Node have a function called "promise" that returns a continuable
>> (instead of a promise)? I'd prefer having third parties provide the
>> promise and continuable APIs over this being the third option.
>> Kris
>

Rasmus Andersson

unread,
Feb 19, 2010, 10:31:31 AM2/19/10
to nod...@googlegroups.com
I created a quick implementation of this:

http://gist.github.com/308779

I've renamed "promise" to "closure" here, but that's just because I
think it's a more suiting name.

Basically, you do this:

asyncOperation(args)(function(err, results){
// do something with error and/or results
})

To close a closure (i.e. promise.emit{Error, Success}) you call close:

function asyncOperation(args) {
var cl = mkclosure();
cl.close(error, results); // << in reality this happens later
return cl;
}

You could of course add extra sugar to allow for calling the closure
directly by adding input checks in the closure function. The above
becomes:

function asyncOperation(args) {
var cl = mkclosure();
cl(error, results); // << in reality this happens later
return cl;
}

These closures can be passed on as callbacks themselves:

function asyncOperation(args) {
var cl = mkclosure();
anotherAsyncOperation(args)(function(err, args) {
// process args
yetAnotherAsyncOperation(args)(closure.close);
}
return cl;
}

Or if the aforementioned call sugar is added:

anotherAsyncOperation(args)(closure);

The closure could easily be extended with chaining and queueing.

--
Rasmus Andersson

Tim Caswell

unread,
Feb 19, 2010, 10:36:25 AM2/19/10
to nod...@googlegroups.com
Excellent discussion everyone. Sorry for my confusing email earlier. I said continuation when I meant "continuable". For clarity, I'm now calling what I was calling "continuables" to "curried-cps". It's just a function that returns a new function in a closure that takes the callback and errback.

+1 for keeping all this higher-level stuff out of node core

Mikeal Rogers

unread,
Feb 19, 2010, 11:20:35 AM2/19/10
to nod...@googlegroups.com
I'm going to disagree with most of this.

Providing either little or no API around the async style will invite a
lot of divergent styles. This sounds good in the short term because
the competition could spur some new and creative solutions.

But the tradeoff here is that different libraries may have radically
different API styles for relatively simple operations requiring the
average programmer to understand and context switch between different
styles when reusing third party modules.

The bonus of having EventEmitter and Promise be first class objects
that ship with node is that you have incentive to use them and keep
your style close to the base node APIs and the majority of the third
party modules.

It's ok for node to have an opinion about what the best async style is
and adopt that style as default. I think that we should go with the
easiest style to understand which IMHO is the current Promise API.

-Mikeal

Mikeal Rogers

unread,
Feb 19, 2010, 11:25:42 AM2/19/10
to nod...@googlegroups.com
2010/2/19 Felix Geisendörfer <fe...@debuggable.com>:

>> I'd prefer having third parties provide the
>> promise and continuable APIs over this being the third option.
>
> +1
>
> I think node should go with returning a function that accepts a single
> callback, and the first parameter is reserved for indicating if there
> was an error.
>
> All high level wrapping and mangling should be done using 3rd party
> libraries.

One really nice thing about node Promises ATM is that explicit
addErrback means that node can throw an exception when the error
doesn't have a handler.

Having this by default has made my development process a lot easier
since anything I didn't explicitly set an error handler on throws an
exception even when async and I don't have some problem way down the
stack.

Passing the error as the first argument means I have nearly as much
code for handling the errors anyway and gives me far worse default
behavior when I don't write the error handler.

-Mikeal

Kris Zyp

unread,
Feb 19, 2010, 11:37:53 AM2/19/10
to nodejs

On Feb 19, 9:20 am, Mikeal Rogers <mikeal.rog...@gmail.com> wrote:
> I'm going to disagree with most of this.
>
> Providing either little or no API around the async style will invite a
> lot of divergent styles. This sounds good in the short term because
> the competition could spur some new and creative solutions.
>
> But the tradeoff here is that different libraries may have radically
> different API styles for relatively simple operations requiring the
> average programmer to understand and context switch between different
> styles when reusing third party modules.

This is great point, Mikeal. Specifically we have already seen a lot
database adapters utilize promises. If one writes a module that can
generically work with the results of a database call, it is much
easier if it knows to expect a certain style of promises. Having to
write code that can work properly with CommonJS promises, the various
different variants on continuables, and anything else people invent is
a lot of extra work.
Kris

Benjamin Thomas

unread,
Feb 19, 2010, 11:38:11 AM2/19/10
to nod...@googlegroups.com
On Fri, Feb 19, 2010 at 9:20 AM, Mikeal Rogers <mikeal...@gmail.com> wrote:
> But the tradeoff here is that different libraries may have radically
> different API styles for relatively simple operations requiring the
> average programmer to understand and context switch between different
> styles when reusing third party modules.

I'm not convinced that is true. As long as libraries adopt Node's
base system (whatever that ends up being) then ALL the libraries will
have a consistent API.

What would then happen is that two different kinds of libraries would
emerge, those offering async services, and then those for dealing with
async services.

So, I could write a library for CouchDB, that matches Node's API. Now
all of a sudden my CouchDB library is compatible with any async lib
(like Promises or Continuations or whatever) that works with Node's
API.

Or I could write a Promise library that works with Node's API, now I
am guaranteed that it will work with any library that matches Node's
base API.

I realize I am being redundant here, but I'm trying to drive home that
by having Node choose a base async API (and encouraging library
developers to do this as well) we are not segmenting the node
libraries and nor are we making people switch back and forth between
different styles. You get to choose the async system you like, AND
you know it will work with Node libraries.

On Fri, Feb 19, 2010 at 9:25 AM, Mikeal Rogers <mikeal...@gmail.com> wrote:
> One really nice thing about node Promises ATM is that explicit
> addErrback means that node can throw an exception when the error
> doesn't have a handler.
>
> Having this by default has made my development process a lot easier
> since anything I didn't explicitly set an error handler on throws an
> exception even when async and I don't have some problem way down the
> stack.
>
> Passing the error as the first argument means I have nearly as much
> code for handling the errors anyway and gives me far worse default
> behavior when I don't write the error handler.
>
> -Mikeal

It doesn't sound to me like the using the base Node libraries would be
for you. It isn't for me necessarily either. We'll just have to
choose a Promise or continuable implementation we like to use on top
of them. We can take that performance hit and abstraction in exchange
ease of use.

Mikeal Rogers

unread,
Feb 19, 2010, 11:58:41 AM2/19/10
to nod...@googlegroups.com
> It doesn't sound to me like the using the base Node libraries would be
> for you.  It isn't for me necessarily either.  We'll just have to
> choose a Promise or continuable implementation we like to use on top
> of them.  We can take that performance hit and abstraction in exchange
> ease of use.

The thing is, I'm having a hard time thinking of an abstraction over
all the noted styles that won't be leaky. Maybe I'm not just not
creative enough.

If the performance hit is substantial, or the abstraction is leaky,
you can expect everyone who favors a particular style to ignore the
abstraction and write directly for the style they like which leads to
the kind of segmentation I'm afraid of.

The question isn't whether or not an abstraction is possible, it's
whether it will actually be used widely and adopted as a defacto
standard.

If node's interface is exceedingly low level in order to enable all
the styles mentioned then it's going to be the least attractive to use
directly and a higher level abstraction that strings together all the
styles that might be implemented on top of node's interface would need
adopt an API that is relatively inflexible in order to accommodate the
variances and would also not be as attractive as using a specific
style. Which means it's still up to the average developer to use this
abstraction to string together all the styles of the modules he/she
wants to use and I just don't see that as a very attractive way to
program.

-Mikeal

inimino

unread,
Feb 19, 2010, 12:19:27 PM2/19/10