Module API design for both promises and callbacks

309 views
Skip to first unread message

Andrew de Andrade

unread,
Apr 15, 2014, 12:20:05 AM4/15/14
to nod...@googlegroups.com
So at work we're working on a bunch of node modules that will eventually be published as open-source and I'm in favor of callbacks and two of my co-workers are in favor of promises. We've discussed supporting both API interfaces and I was curious what the general consensus of the community was with respect to supporting both and the best way to name functions and methods to support both.

That being said, there are three obvious choices:

(a) two function types: (1) synchronous functions; and (2) async functions that return promises but also handle callbacks

var value = myFunctionSync();
myFunction(callback);
var promise = myFunction();

this approach has a tiny performance overhead (since you have to check if the last argument is a function to determine if you should return a promise or execute that function as the callback) and makes all the functions a little convoluted (unless you make one higher order function that you apply to all your callback functions to support both APIs). Furthermore async, higher order, overloaded functions or variable arity functions become impossible since you can't necessarily assume that the last argument is always the callback. 

(b) three function types: (1) synchronous functions; (2) async callback functions; and (3) async promise functions

var value = myFunctionSync();
myFunction(callback);
var promise = myFunctionDeferred();

this is ugly but explicit in terms of what to expect and permits the most flexibility.

(c) two function types:  (1) synchronous functions; (2) async callback functions;

var value = myFunctionSync();
myFunction(callback);

and promise support is left up to the user by using a nodeify() method from a promise library. This is my preference, but won't make my co-workers happy. 


With all this in mind, what's the general consensus of the NodeJS community on this issue? I searched google and the archives and could not find any blog posts or discussions that address this particular issue. What are the pros and cons of each approach? What if any libraries implement options (a) or (b)? etc.


Mikeal Rogers

unread,
Apr 15, 2014, 12:23:07 AM4/15/14
to nod...@googlegroups.com
module.exports = function (cb) {
  var promise = wrapThisCallbackInAPromise(cb)
  return promise
}

Always observe the standard callback patter for the function you export. Always export a function as your module's API.

-Mikeal

--
--
Job Board: http://jobs.nodejs.org/
Posting guidelines: https://github.com/joyent/node/wiki/Mailing-List-Posting-Guidelines
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?hl=en

---
You received this message because you are subscribed to the Google Groups "nodejs" group.
To unsubscribe from this group and stop receiving emails from it, send an email to nodejs+un...@googlegroups.com.
For more options, visit https://groups.google.com/d/optout.

mog...@syntheticsemantics.com

unread,
Apr 15, 2014, 12:24:05 PM4/15/14
to nod...@googlegroups.com
I think option B (three function types) is best because I believe "things that are different should look different": it reduces surprises, particularly for new users or when working on code which is new to you.  

The way promises were added to JS requires different implementations for the callback and promise versions, and those two functions have very different calling sequences so there's no economy to be had by overloading the function name.

By way of comparison, promises/futures were in a C compiler I used about 15 years ago but this problem didn't come up because "future" was a type attribute of the variable being assigned, not part of the function being called, so any expression could be used as a future and the compiler knew a reference to the variable implied an await().

Lacking type attributes this isn't possible in JS, and keeping track of "value or promise" is the programmer's responsibility, so syntax that helps me keep that straight is helpful.

             -J

Sean McArthur

unread,
Apr 15, 2014, 11:25:37 PM4/15/14
to nod...@googlegroups.com
It's not too hard to support both:

function promisify(p, callback) {
  return p.then(function(val) {
    callback(null, val);
  }, function(reason) {
    callback(reason);
  });
}

function somethingAsync(arg1, arg2, callback) {
  var d = defer();

  // do async stuff

  return callback ? promisify(d.promise, callback) : d.promise;
}

willem dhaeseleer

unread,
Apr 16, 2014, 2:46:48 AM4/16/14
to nod...@googlegroups.com

Andrew,

For the love of all that is dear to us, Use promises, do not support callbacks, don't even think about supporting both.
There is a reason why promises are becoming part of the standard in ECMA 6.

Here are a few of many reasons why to choose promises:

- It prevent deep indentation
- It always you to pass on asynchronous operations
- Asyncronous callstacks and consistent error handling ( you want this )
    - How many types have you typed if (err) throw err or if (err) console.warn(err) ?
- Refactoring in callback styled code is extremely tedious to the point where it would be almost reasonable to say it's impossible
- Improved readability trough more logical control flow
- Integration with coroutines ( you want this )

greelgorke

unread,
Apr 16, 2014, 3:50:22 AM4/16/14
to nod...@googlegroups.com
inline


Am Mittwoch, 16. April 2014 08:46:48 UTC+2 schrieb willem dhaeseleer:

Andrew,

For the love of all that is dear to us, Use promises, do not support callbacks, don't even think about supporting both.
There is a reason why promises are becoming part of the standard in ECMA 6.

they are there to give you an alternative, not a replacement. Callbacks are simple for simpler things. they are the core pattern and they are accepted. every single person new to node, can just use them, as soon she understood async coding style.

it is a very bad habbit to only provide promises api. one of the top3 popular modules on npm is async, which handles callbacks.

So, stop crying about callbacks, learn them and provide a cb-based interface. and stop saying us. :P
 

Here are a few of many reasons why to choose promises:

- It prevent deep indentation
flatten your code.
- It always you to pass on asynchronous operations
huh?
- Asyncronous callstacks and consistent error handling ( you want this )
    - How many types have you typed if (err) throw err or if (err) console.warn(err) ?
you actually type this yourself?
- Refactoring in callback styled code is extremely tedious to the point where it would be almost reasonable to say it's impossible
it always hard to refactor bad written code either with callbacks, promises or even synchronous code.
- Improved readability trough more logical control flow
duh. readability is subjective.
- Integration with coroutines ( you want this )
huh? how is that connected?

willem dhaeseleer

unread,
Apr 16, 2014, 4:27:18 AM4/16/14
to nod...@googlegroups.com

Hey greelgorke,

Great to get some feedback on my answer, I'll try to clarify my arguments some more here:

- It always you to pass on asynchronous operations
huh? 
  
 // foo returns promise
 var futureBar = foo();

 // you can know pass around futureBar to some other api or use it for later reference
 // with callbacks you will have to write your own wrapper code to get this type of "asynchronous encapsulation"

    - How many types have you typed if (err) throw err or if (err) console.warn(err) ?
you actually type this yourself? 

Off course not, but i have seen it in to much code already. 
Obviously i forgot if (err) return callback(err);
If haven't written in this style anymore for a long time.

- Improved readability trough more logical control flow
duh. readability is subjective.

Off course it's subjective, but chronological reading order is something I tend to value in most code.
Just my opinion.

- Integration with coroutines ( you want this )
huh? how is that connected? 

An example should clarify this, this uses bluebird:
This is obviously a bad use of a database, but the idea is to demonstrate how promises integrate with coroutines.

var getTotalFriendBalance = Promise.coroutine(function* (name) {
    var user, userFriends, x, totalBalance;
    user = yield db.getUserByName(name);
    userFriends = yield db.getFriends(user.id);
    for (x = 0; x < userFriends.length; x++) {
        totalBalance += (yield db.getAccountInfo(userFriends[x].id)).balance;
    }
    return totalBalance;
});

I challenge you to write this peace of code with only callbacks, I think you will find this syntax is much more intuitive and more pleasant to write.
This is only possible because all asynchronous methods here return promises (or thenables) that can be used by the coroutine.

I hope this clarifies my personal opinion on why promises are better. 

greelgorke

unread,
Apr 16, 2014, 6:02:20 AM4/16/14
to nod...@googlegroups.com
my only concern about your post is that you simply ignore the standards in node. node core apis are callback based, your 3rd party libs should honor this. a good api doesn't care much about personal opinions and a) supports the standard and b) provides optional methods for convinience.

it's not about whats better. its about what a good api 

willem dhaeseleer

unread,
Apr 16, 2014, 6:34:35 AM4/16/14
to nod...@googlegroups.com
That fact that node core api's only support callbacks doesn't make them holy. I understand they used callbacks back in 2009 before the proliferation  of asynchronous control flow in javascript and the state of promises in V8 / ECMAScript . But today promises are in V8 and wildly used and you just can't argue with increase in readability, maintenance and productivity. 
I'm sure we could have a lengthy discussion about what makes a good api, but I think most people will agree with me that consistency should be key. Providing both promises and callbacks in your api seems like a very bad way to go.

The node core API also doesn't really define a standard, it defines an interface, I believe there are even some methods in the api that don't even respect the callback(err, result) format.  
The standard is ECMAScript, and ECMAScript 6 has promises, and generators, use them where applicable.







--
--
Job Board: http://jobs.nodejs.org/
Posting guidelines: https://github.com/joyent/node/wiki/Mailing-List-Posting-Guidelines
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?hl=en

---
You received this message because you are subscribed to a topic in the Google Groups "nodejs" group.
To unsubscribe from this topic, visit https://groups.google.com/d/topic/nodejs/NpZ4WT1eOnw/unsubscribe.
To unsubscribe from this group and all its topics, send an email to nodejs+un...@googlegroups.com.

Charlie McConnell

unread,
Apr 16, 2014, 9:05:44 AM4/16/14
to nod...@googlegroups.com
I would like to argue with the increase in readability - it doesn't exist.
 
Promises are an overly verbose "solution" to a simple problem, and are not an appropriate global replacement for callbacks in every case.  Saying so is misleading and disingenuous.
 
If you want something universally usable, use callbacks, and let the consumers of your library wrap them in all the promises they want to.  Wrapping a callback in a promise is less work than taking apart a promise into a callback, making this the most widely useful approach.
 
Using solely promises is only going to contribute to the increasing fragmentation of this community into sects, each revolving around its (primarily cosmetic) abstractions of choice.
 
--
Charlie McConnell
You received this message because you are subscribed to the Google Groups "nodejs" group.
To unsubscribe from this group and stop receiving emails from it, send an email to nodejs+un...@googlegroups.com.

willem dhaeseleer

unread,
Apr 16, 2014, 9:26:21 AM4/16/14
to nod...@googlegroups.com
Charlie, 

I actually agree that promises them self don't really contribute to much to the readability of code. 
It's only when you combine them with generators to create co-routines that you truly benefit from improved readability and asynchronous callstacks / error propagation which can be really helpful during debugging as well. 






willem dhaeseleer

unread,
Apr 16, 2014, 9:34:37 AM4/16/14
to nod...@googlegroups.com
Using just promises with a good library will off course already give you the asynchronous callstacks / error propagation. 
So it's really only the coroutines that are improving readability / maintainability, but you can't have coroutines without promises, or some sort of abstraction around callbacks.

Alex

unread,
Apr 17, 2014, 2:01:03 AM4/17/14
to nod...@googlegroups.com
Concerning Promises and NodeJS history:

When I first started lurking in 2010, NodeJS was using promises. At first it sounded cool but I couldn't figure them out and gave up. About a year later I started lurking again and discovered, lo and behold, that promises had been jettisoned. It was a nightmare of complexity. They are extremely opaque and easily breed expressions that are very difficult to figure out "what's really going on." Also there were a bunch of different versions of promises, creating fragmentation.

NodeJS makes pragmatic decisions and always optimizes for community involvement. Dahl originally chose javascript after evaluating a lot of languages because it was designed for simplicity, pragmatism, and these features facilitate community involvement. A simple callback passing style proved to be more in line with Node's core principles. It was more "nodesque" (don't shoot me please :-).

Brendan Eich, author of JavaScript, encapsulates pretty well the simplicity and malleable nature of JS:

As is well known at this point, I created JavaScript in ten days in May 1995, under duress and conflicting management imperatives--"make it look like Java,"make it easy for beginners," "make it control almost everything in the Netscape browser".

Apart from getting two big things right (first-class functions, object prototypes), my solution to the challenging requirements and crazy-short schedule was to make JavaScript extremely malleable from the start. 

(Quoted from Foreword to Effective JavaScript, page xiii)

Arguably, the callback passing style in NodeJS is also optimized for malleability and extensibility. E.g., Bruno Jouhier developed a system called "streamline" that depends on the consistent API callback style to facilitate linear programming without promises. (A separate process compiles the code that uses streamline, which is linear and resembles synchronous code, into callback-oriented javascript.) I think Bruno's doing a thing with ES5 generators now, for the record, but my point is that something like streamline shows that a consistent and simple style reaps long-term extensibility.

In summary, as I understand it, JavaScript was chosen for NodeJS because its community and its leaders (thus far Ryan Dahl, Isaac Schlueter, and Timothy Fontaine) preferred the system of asynchronous callbacks. It's fine if you don't like it -- you can use promises or other approaches to make asynchronous programming look like synchronous programming. There's no dogma. However, most people writing an API are probably better off to follow the basic guidelines. Because, generally speaking, if you want other people to use your code, it's best to keep your API as simple, minimal, consistent, and easy to use as possible.

In short, if you ask for advice, most seasoned NodeJS developers will recommend that you use the standard callback style for your API. If you google through the NodeJS threads, that is what you'll find advocated by the most prominent members, such as Mikeal Rogers (who commented earlier).

Alex

Alex Kocharin

unread,
Apr 17, 2014, 3:13:03 PM4/17/14
to nod...@googlegroups.com
 
You *can* have coroutines without promises. Good coroutine wrappers will just curry any yielded function assuming that the last argument is a callback.
 
 
16.04.2014, 17:34, "willem dhaeseleer" <dhwi...@gmail.com>:

Alex Kocharin

unread,
Apr 17, 2014, 3:26:17 PM4/17/14
to nod...@googlegroups.com
 
16.04.2014, 12:27, "willem dhaeseleer" <dhwi...@gmail.com>:
 
Hey greelgorke,
 
Great to get some feedback on my answer, I'll try to clarify my arguments some more here:
 
- It always you to pass on asynchronous operations
huh? 
  
 // foo returns promise
 var futureBar = foo();
 
 // you can know pass around futureBar to some other api or use it for later reference
 // with callbacks you will have to write your own wrapper code to get this type of "asynchronous encapsulation"
 
In this case API consumer would wrap callbacks provided by an API with promises. API doesn't have to provide promises itself.
 
And since I never saw this use-case in practice it's not really very important.
 
 
 
    - How many types have you typed if (err) throw err or if (err) console.warn(err) ?
you actually type this yourself? 
 
Off course not, but i have seen it in to much code already. 
Obviously i forgot if (err) return callback(err);
If haven't written in this style anymore for a long time.
 
- Improved readability trough more logical control flow
duh. readability is subjective.
 
Off course it's subjective, but chronological reading order is something I tend to value in most code.
Just my opinion.
 
- Integration with coroutines ( you want this )
huh? how is that connected? 
 
An example should clarify this, this uses bluebird:
This is obviously a bad use of a database, but the idea is to demonstrate how promises integrate with coroutines.
 
var getTotalFriendBalance = Promise.coroutine(function* (name) {
    var user, userFriends, x, totalBalance;
    user = yield db.getUserByName(name);
    userFriends = yield db.getFriends(user.id);
    for (x = 0; x < userFriends.length; x++) {
        totalBalance += (yield db.getAccountInfo(userFriends[x].id)).balance;
    }
    return totalBalance;
});
 
I challenge you to write this peace of code with only callbacks, I think you will find this syntax is much more intuitive and more pleasant to write.
 
Easy:
 
var getTotalFriendBalance = require('co')(function* (name) {
    var user, userFriends, x, totalBalance;
    user = yield db.getUserByName.bind(db, name);
    userFriends = yield db.getFriends.bind(db, user.id);
    for (x = 0; x < userFriends.length; x++) {
        totalBalance += (yield db.getAccountInfo.bind(db, userFriends[x].id)).balance;
    }
    return totalBalance;
})();
 
 
--

willem dhaeseleer

unread,
Apr 17, 2014, 3:27:52 PM4/17/14
to nod...@googlegroups.com
Alex,

Do you have an example of any coroutine / promise library that supports passing in an callback styled function without wrapping / binding ?
You would have to pass in the context and arguments separately for every every call, that seems very cumbersome to me.

You could yield a binded function to improve it somehow, but binding on every call sounds like a bad idea as well.
In any event, I think that pre-wrapping callback styled functions is a requirement to deal efficiently with coroutines in javascript.

Alex Kocharin

unread,
Apr 17, 2014, 3:35:13 PM4/17/14
to nod...@googlegroups.com
 
17.04.2014, 23:28, "willem dhaeseleer" <dhwi...@gmail.com>:
Alex,
 
Do you have an example of any coroutine / promise library that supports passing in an callback styled function without wrapping / binding ?
 
I didn't really look for one, maybe "galaxy" will count.
 
 
You would have to pass in the context and arguments separately for every every call, that seems very cumbersome to me.
 
You could yield a binded function to improve it somehow, but binding on every call sounds like a bad idea as well.
 
So is it basically the same deal as creating Promise object for every call?

willem dhaeseleer

unread,
Apr 17, 2014, 3:35:18 PM4/17/14
to nod...@googlegroups.com
You beat me to it with your example, still i think that binding on every call is a bad idea.

Including the context every time is unpleasant, and it will get even worse when you want to use apply.
It also does not improve the readability of the code ( .bind is just creating noise all over the place ).





willem dhaeseleer

unread,
Apr 17, 2014, 3:39:12 PM4/17/14
to nod...@googlegroups.com
See my counter arguments for bind in the previous post. Wrapping will always be necessary when dealing with callback styled functions and coroutines. That in it self should be an argument against using callback style, imho.

Also, promises allow for consistent asynchronous error handling at the language level, I like that.

Bruno Jouhier

unread,
Apr 17, 2014, 6:13:50 PM4/17/14
to nod...@googlegroups.com


On Thursday, April 17, 2014 9:39:12 PM UTC+2, willem dhaeseleer wrote:
See my counter arguments for bind in the previous post. Wrapping will always be necessary when dealing with callback styled functions and coroutines. That in it self should be an argument against using callback style, imho.

As Alex mentioned galaxy (and streamline a bit before), I'll jump in.

You complain about wrapping, but promises also force you to introduce wrappers: in your code you need a Promise.coroutine wrapper around every function that you define.

With galaxy, you don't need promises and you don't need wrappers around every function definition. There is zero noise (zero 3rd party library calls in your code) as long as you deal with galaxy functions calling other galaxy functions. So your example becomes:

function* getTotalFriendBalance(name) {
    var user = yield db.getUserByName( name);
    var userFriends = yield db.getFriends(user.id);
    var totalBalance = 0;
    for (var x = 0; x < userFriends.length; x++) {
        totalBalance += (yield db.getAccountInfo(userFriends[x].id)).balance;
    return totalBalance;
}
 
// And you can call it as:
 
function* compareBalances(name1, name2) {
    return (yield getTotalFriendBalance(name1)) -  (yield getTotalFriendBalance(name2));
}
 
No wrappers, no promises, no preprocessor, no C++ module under the hood, just function*s calling other function*s with yield!

The only places where you will see galaxy library calls in your code are:
  • when your galaxy functions call normal callback based node APIs. You need to wrap the callback API with galaxy.star (so my example above assumes that the db API was written with galaxy).
  • when your galaxy functions are called from normal callback based node APIs. You need to unwrap them with galaxy.unstar
  • when you parallelize: you use galaxy.spin instead of yield.

If most of your code is made of galaxy functions calling other galaxy functions (which is the case when you write complex modules) you get very lean code. I'm ready to bet that this code will be faster than code written with promise/coroutine combination because 1) you save the allocation of a wrapper around every function being defined (important if you pass or return async functions in your APIs) and 2) you save the allocation of a promise object in every call. The heart of the galaxy library is a tight loop that deals directly with generator objects.

And BTW, I don't use galaxy directly, I use it indirectly through streamline because 1) streamline gives me a friendlier syntax (yield does not play well in expressions because it is prefix with low precedence) and 2) it is directly compatible with node's callback APIs (no wrappers at all as long as callbacks have the standard node signature).

As Alex (and others) mentioned a few posts above, the callback API is a key convention in the node ecosystem. It is awfully simple and it allows modules to interoperate with each other without any extra wrappers. Promises may be interesting conceptually but they introduce complexity (the post that started this thread). Most of the time you don't need the full power of the promise API. Instead of having your APIs return a full-fledged promise object, you could have it return a simple "function(cb)" that you can call later with a callback to get a result. This is lighter and fully aligned with node's conventions (this is what I call a "future" in streamline). 


Also, promises allow for consistent asynchronous error handling at the language level, I like that.

You don't need promises for that; generators are sufficient. I do my error handling with try/catch.

Bruno
 

willem dhaeseleer

unread,
Apr 17, 2014, 6:59:16 PM4/17/14
to nod...@googlegroups.com


It seems to me that galaxy just tries to be an extremely lightweight promise library pretty much by ignoring the promise standard.

If you just define your asynchronous functions as generators you expect the consumer to know its an async function and not a normal generator. Coroutine's should by opaque promises just like any other async function for consistency.

I actually like the coroutine wrapper from blue bird because its a very clear annotation.
I'm also not convinced by your performance remark, the coroutine function is only called once so doesn't add any complexity, and promises are native language constructs. We need benchmarks.

Also generators by them self have nothing to do with async stacks, you need some sort tracking in the promises or the coroutine runner.

The callback "convention" has  absolutely zero standardisation and even tough it is simple its very error prone. It requires you to mix language constructs  inside your function interface's, I don't get why anyone would want to adhere to that.

Bruno Jouhier

unread,
Apr 18, 2014, 2:07:29 AM4/18/14
to nod...@googlegroups.com


On Friday, April 18, 2014 12:59:16 AM UTC+2, willem dhaeseleer wrote:


It seems to me that galaxy just tries to be an extremely lightweight promise library pretty much by ignoring the promise standard.

What galaxy demonstrates is that generators are sufficient. You can do all you want with generators, you don't need promises (or any other abstraction) around them.
 

If you just define your asynchronous functions as generators you expect the consumer to know its an async function and not a normal generator. Coroutine's should by opaque promises just like any other async function for consistency.

In the node.js ecosystem "any other async function" is a function with a callback, not a function that returns a promise.
 

I actually like the coroutine wrapper from blue bird because its a very clear annotation.
I'm also not convinced by your performance remark, the coroutine function is only called once so doesn't add any complexity, and promises are native language constructs. We need benchmarks.

If you want an annotation, just add a comment.

Regarding performance, function wrappers are not always static. You'll allocate wrappers dynamically if you do functional programming with async functions. And the overhead of an extra promise allocation in every call is always there. Anyway, if you really care about performance you'll stay away from generators for now and stick to raw callbacks (or fibers which do better with special code patterns).

Also generators by them self have nothing to do with async stacks, you need some sort tracking in the promises or the coroutine runner.

That's what I'm challenging with galaxy. It shows that you can get async/await semantics with just generators. Promises are not necessary.
 


The callback "convention" has  absolutely zero standardisation and even tough it is simple its very error prone. It requires you to mix language constructs  inside your function interface's, I don't get why anyone would want to adhere to that.

It is the de facto standard in the node.js ecosystem. And it is more efficient!

Promises are disruptive and they don't bring much value because you can do without them (as galaxy and other generator based libs - suspend, co - demonstrate).

willem dhaeseleer

unread,
Apr 18, 2014, 3:33:00 AM4/18/14
to nod...@googlegroups.com

What galaxy demonstrates is that generators are sufficient. You can do all you want with generators, you don't need promises (or any other abstraction) around them.
In the node.js ecosystem "any other async function" is a function with a callback, not a function that returns a promise.

Claiming that promises ( or any other abstraction ) or not required seems false to me. Here is an example from galaxy's readme:

function* projectLineCountsParallel() {
    var future1 = galaxy.spin(countLines(__dirname + '/../examples'));
    var future2 = galaxy.spin(countLines(__dirname + '/../lib'));
    var future3 = galaxy.spin(countLines(__dirname + '/../test'));
    var total = (yield future1()) + (yield future2()) + (yield future3());
    console.log('TOTAL: ' + total);
    return total; 
}

These future's that galaxy uses are in fact abstractions around an asynchronous process, just like promises. 
The only difference being that they are only useful inside galaxy and they do not conform to a standard, and they require you to use a special wrapper function (spin).

It doesn't seem like a good idea to me that you would have to rewrite the way you call a function in order to affect parallelism.
With promises you ether yield, or don't, and keep the promises for reference. No need for a special spin function.
( The requirement to call a future when yielding it seems somewhat cumbersome to.)
This proves pretty clearly that you need some sort of an abstraction. 

If you want an annotation, just add a comment.

You can't really efficiently say in a comment that the generator is in fact a coroutine that requires an invoke trough a specific library.
A regular coroutine should be just a function, like any other async operation should be.

Regarding performance, function wrappers are not always static...

True, but let's not drag in actual performance in the discussion. We need benchmarks to make any sort of point around them and I think the discussion is more about api design then what V8 is capable of optimizing.

It is the de facto standard in the node.js ecosystem. And it is more efficient!

de facto != de jure, that's my point, standardization is good, it allows for a more stable ecosystem.

The fact that you have to unstar a function that comes from galaxy if i want to do an invocation from outside the library seems very uncanny.
It's bad enough we have to wrap the core api's for coroutine management, now we have to wrap functions from the library that tries to solve that as well, It's only adding to the problem.



On 18 April 2014 08:07, Bruno Jouhier <bjou...@gmail.com> wrote:


On Friday, April 18, 2014 12:59:16 AM UTC+2, willem dhaeseleer wrote:


It seems to me that galaxy just tries to be an extremely lightweight promise library pretty much by ignoring the promise standard.

I actually like the coroutine wrapper from blue bird because its a very clear annotation.

Bruno Jouhier

unread,
Apr 18, 2014, 7:55:46 AM4/18/14
to nod...@googlegroups.com


On Friday, April 18, 2014 9:33:00 AM UTC+2, willem dhaeseleer wrote:

What galaxy demonstrates is that generators are sufficient. You can do all you want with generators, you don't need promises (or any other abstraction) around them.
In the node.js ecosystem "any other async function" is a function with a callback, not a function that returns a promise.

Claiming that promises ( or any other abstraction ) or not required seems false to me. Here is an example from galaxy's readme:
function* projectLineCountsParallel() {
    var future1 = galaxy.spin(countLines(__dirname + '/../examples'));
    var future2 = galaxy.spin(countLines(__dirname + '/../lib'));
    var future3 = galaxy.spin(countLines(__dirname + '/../test'));
    var total = (yield future1()) + (yield future2()) + (yield future3());
    console.log('TOTAL: ' + total);
    return total; 
}

These future's that galaxy uses are in fact abstractions around an asynchronous process, just like promises. 
The only difference being that they are only useful inside galaxy and they do not conform to a standard, and they require you to use a special wrapper function (spin).

Not quite: these futures are no abstractions, they are just generator functions aka function*!

A galaxy API does not expose idiosyncratic types/abstractions, it exposes "standard" types defined by the JS language. For example galaxy.spin transforms a generator object (returned by a call to a function*) into a generator function (a function*) on which you can yield later to get a result.


It doesn't seem like a good idea to me that you would have to rewrite the way you call a function in order to affect parallelism.
With promises you ether yield, or don't, and keep the promises for reference. No need for a special spin function.
( The requirement to call a future when yielding it seems somewhat cumbersome to.)
This proves pretty clearly that you need some sort of an abstraction. 

Galaxy is rather a helper library that manipulates the abstractions that are already there in the language.
 

If you want an annotation, just add a comment.

You can't really efficiently say in a comment that the generator is in fact a coroutine that requires an invoke trough a specific library.
A regular coroutine should be just a function, like any other async operation should be.

Yes you can: // async
 
 

Regarding performance, function wrappers are not always static...

True, but let's not drag in actual performance in the discussion. We need benchmarks to make any sort of point around them and I think the discussion is more about api design then what V8 is capable of optimizing.

It is the de facto standard in the node.js ecosystem. And it is more efficient!

de facto != de jure, that's my point, standardization is good, it allows for a more stable ecosystem.

Yes. What do you prefer: a simple de facto standard, or a sophisticated de jure standard? I prefer the former.


The fact that you have to unstar a function that comes from galaxy if i want to do an invocation from outside the library seems very uncanny.
It's bad enough we have to wrap the core api's for coroutine management, now we have to wrap functions from the library that tries to solve that as well, It's only adding to the problem.

That's why I'm not using galaxy directly. I'm using streamline which works directly with node.js callbacks.

If your APIs return promises, you'll need to provide wrappers to transform both ways between promises and callback-based APIs. Same problem. Unless you manage to convince the whole node community to switch to promises. Good luck!

Bruno

willem dhaeseleer

unread,
Apr 18, 2014, 8:46:29 AM4/18/14
to nod...@googlegroups.com
 // async

I think that is very verbose, It will not be very helpful to someone who doesn't know galaxy well or even does know coroutines. ( is it a coroutine, is it a generator ? its both ! )

If your APIs return promises, you'll need to provide wrappers to transform both ways between promises and callback-based APIs.

Bruno, that is simply not true. If a library returns promises u can just use the promise. You will always need to write more code  to convert a promise to a callback style function then to just use the promise. 

 I'm using streamline which works directly with node.js callbacks.

Streamline.js also provides "futures", also by using a special syntax. !_
( which is also noise imo )

So if you use both libs, you are using two different type of control flow libraries ( of which one is a pre-processor ) , that both allow you to create async encapsulations with special syntaxes, to workaround callbacks.

I think I will just stick to promises.
I can't help but feel that they are more uniform, cleaner and how ECMAScript is intending async control flow in the future.












--

Bruno Jouhier

unread,
Apr 18, 2014, 12:57:05 PM4/18/14
to nod...@googlegroups.com


On Friday, April 18, 2014 2:46:29 PM UTC+2, willem dhaeseleer wrote:
 // async

I think that is very verbose, It will not be very helpful to someone who doesn't know galaxy well or even does know coroutines. ( is it a coroutine, is it a generator ? its both ! )

If your APIs return promises, you'll need to provide wrappers to transform both ways between promises and callback-based APIs.

Bruno, that is simply not true. If a library returns promises u can just use the promise. You will always need to write more code  to convert a promise to a callback style function then to just use the promise. 

To clarify, I meant that nodejs developers will want these wrappers.
 

 I'm using streamline which works directly with node.js callbacks.

Streamline.js also provides "futures", also by using a special syntax. !_
( which is also noise imo )

Yes but a streamline future is a standard nodejs callback-based function. You can use it as future(function(err, result) { ... }).

You don't need all the sophistication of promises to provide promise-like functionality (the essence of it, i.e. the ability to encapsulate a computation which will yield a result later). You can do it with a simple callback-based design, by currying the callback.
 

So if you use both libs, you are using two different type of control flow libraries ( of which one is a pre-processor ) , that both allow you to create async encapsulations with special syntaxes, to workaround callbacks.

I think I will just stick to promises.
I can't help but feel that they are more uniform, cleaner and how ECMAScript is intending async control flow in the future.

I did not jump in to convince you to use galaxy or streamline (I got bored with this kind of exercise), rather to develop Alex' point that "you *can* have coroutines without promises". Promises don't really cut it when it comes to solving the "sync-style coding" challenge. What cuts it is generators (fibers and preprocessors too).

Bruno

Aria Stewart

unread,
Apr 18, 2014, 2:54:10 PM4/18/14
to nod...@googlegroups.com


I think I will just stick to promises.
I can't help but feel that they are more uniform, cleaner and how ECMAScript is intending async control flow in the future.

I did not jump in to convince you to use galaxy or streamline (I got bored with this kind of exercise), rather to develop Alex' point that "you *can* have coroutines without promises". Promises don't really cut it when it comes to solving the "sync-style coding" challenge. What cuts it is generators (fibers and preprocessors too).

Exactly: Promises give you something else, without being ‘sync’-style. They let you manipulate as-yet-unavailable values relatively transparently. Actual order of operations can be factored out instead of made explicit; what’s left is order of dependency, rather than order of operations.

signature.asc

Bruno Jouhier

unread,
Apr 19, 2014, 5:33:10 AM4/19/14
to nod...@googlegroups.com


On Friday, April 18, 2014 8:54:10 PM UTC+2, Aria Stewart wrote:

Exactly: Promises give you something else, without being ‘sync’-style. They let you manipulate as-yet-unavailable values relatively transparently. Actual order of operations can be factored out instead of made explicit; what’s left is order of dependency, rather than order of operations.

 
My other point was that you don't need promises for this; you can do it in a very light/simple way with callback-based APIs, just by currying the callback. See http://bjouhier.wordpress.com/2011/04/04/currying-the-callback-or-the-essence-of-futures/

So why bring a new API style into the node ecosystem when it can all be done with wh?

Bruno

Bruno Jouhier

unread,
Apr 19, 2014, 5:34:24 AM4/19/14
to nod...@googlegroups.com
Hit the wrong key too quickly. The end of the sentence is "when it can all be done with what we already have".

spion

unread,
Apr 19, 2014, 5:42:50 AM4/19/14
to nod...@googlegroups.com


On Tuesday, April 15, 2014 6:20:05 AM UTC+2, Andrew de Andrade wrote:
So at work we're working on a bunch of node modules that will eventually be published as open-source and I'm in favor of callbacks and two of my co-workers are in favor of promises. We've discussed supporting both API interfaces and I was curious what the general consensus of the community was with respect to supporting both and the best way to name functions and methods to support both.

That being said, there are three obvious choices:

(a) two function types: (1) synchronous functions; and (2) async functions that return promises but also handle callbacks
(b) three function types: (1) synchronous functions; (2) async callback functions; and (3) async promise functions
(c) two function types:  (1) synchronous functions; (2) async callback functions;


I use promises with Bluebird and I prefer option (c), as long as you also expose all the prototypes of your library. Bluebird has an excellent automated wrapper called promisifyAll [1] that creates functions with the suffix Async which return promises, but it does require that your library uses prototypes instead of creating new objects every time. 

This wrapper also allows you to distinguish operational errors from programmer errors (if you want to). Errors passed via callbacks are given an additional property `isAsync = true` and the promises returned by bluebird have an `.error(handler)` method that allows you to capture only errors tagged with this additional property. For now, this flag is just a bluebird internal convention and not yet "standardized" in promise libraries. In light of the errors article [2], perhaps we should rename the flag to `isOperational` and make it an official public convention of bluebird - in the hope that other promise libraries also adopt it.

Why I think promises (together with arrow function syntax) are better than callbacks or generators isn't really relevant to this discussion, so I decided not to post it here but in a gist instead [3]


 
Reply all
Reply to author
Forward
0 new messages