[ANN]: Deferred - Maintainable asynchronous JavaScript with promises

603 views
Skip to first unread message

Mariusz Nowak

unread,
Sep 22, 2012, 2:50:44 PM9/22/12
to nod...@googlegroups.com
Deferred is (another) promise solution for Node.js (and JavaScript in general). Originally inspired by Q, however more weight is put on functional capabilities of JavaScript and in result many things are solved differently.

Everything is well documented at https://github.com/medikoo/deferred.

It's not that new library, but version that just rolled out (v0.6) in my opinion is ground breaking both in terms of performance and functionalities when compared with other similar solutions.

Some of it's advantages in short:

- Fastest of all popular promise solutions I know (about 5 times faster than Q, and about 2 times faster than jQuery.Deferred)
- Provides convenient way to work with Node.js asynchronous functions, technically when configured, deferred is nearly invisible in your codebase
- All needed collection processors
- Possibility to limit concurrency on any function basis
- Promises are also event emitters, so you can emit progress events or stream results partially as they resolve
- Debugging tools that allows to track unresolved promises and/or gather usage statistics

.. and many more, best source of information is documentation on github

From my experience if you need to configure really complex asynchronous flow that spans between modules, you need to use some extra abstraction layer on top of callbacks. I found promises as most powerful pattern and with Deferred implementation I haven't yet approached any combination that couldn't be easily solved.

npm install deferred

Cheers!

Jeff Barczewski

unread,
Oct 1, 2012, 1:41:56 PM10/1/12
to nod...@googlegroups.com
How does your new version compare against https://github.com/cujojs/when ?

I heard about this from the lxjs but have not used it yet.

It was said that it was compatible with jQuery.Deferred API, but also claims to be very fast.

I guess next time you run your benchmarks, you might add it in to see how it compares.

If you take a look at it, I'd be interested in your thoughts and where your module might excel versus the other.

Mariusz Nowak

unread,
Oct 1, 2012, 3:46:19 PM10/1/12
to nod...@googlegroups.com
Jeff,

Surprisingly I didn't know that one, I've added it to benchmarks, it turned out to be slower than jQuery's implementation, and what's weird and disturbing is that running concurrent calls using When is slower than queued calls one after another: https://github.com/medikoo/deferred#performance

In terms of functionality its API looks fully compliant with Promise/A spec. Deferred is not that strict in that case, Deferred is more function centered, e.g. promise is function itself (it's promise.then).

Create defer (unresolved promise):
// Deferred
var def = deferred();
// When
var def = when.defer();

Create resolved promise with valid value:
// Deferred
var resolved = deferred(value);
// When
var resolved = when.resolve(value);

Create rejected promise:
// Deferred
var rejected = deferred(error);
// When
var rejected = when.reject(error);

When I look at other functionalities they are also solved bit differently, but I think it's no point to describe every difference. If you're after anything specific just ask, I'll be happy to explain :)

What I've noticed and what's not great with When (I would even say it's a design error) is that API doesn't provide function that ends flow. This is very important as otherwise you're locked in try clause and unexpected error would be silent.

Imagine you're just after final value of obtained promise. Handle it with When:

promise.then(function (value) {
  // do something with value
  // but.. any error that might happen here will be catch and silent
});

So we've got an issue, technically you can just handle it by calling then on new promise that was returned, but why we actually need another promise for that?.. and still it's not perfect:

promise.then(function (value) {
  // do something with value
}).then(function () {}, function (err) {
  throw err; // it won't work, it's again in try catch clause
});

So what you really need to do to expose eventual error is:

promise.then(function (value) {
  // do something with value
}).then(function () {}, function (err) {
  process.nextTick(function () {
      throw err;  // Finally thrown!
  });
});

It's not great.

Q took that into account and provided end() function for that:

promise.then(function (value) {
  // do something with value
}).end(); // any eventual error will be exposed

Still with Q we create that extra promise that we don't need and that affects performance.

In Deferred I solved it by making end function with same signature as then:

promise.end(function (value) {
   // do something with value
});

If onerror callback is not provided, eventual error will just throw. So no extra (not needed) promise is created and any error either returned by promise or thrown in success handler will be exposed, that's what you should expect.

Domenic Denicola

unread,
Oct 2, 2012, 9:32:13 AM10/2/12
to nod...@googlegroups.com


On Monday, October 1, 2012 3:46:19 PM UTC-4, Mariusz Nowak wrote:

In Deferred I solved it by making end function with same signature as then:

promise.end(function (value) {
   // do something with value
});

If onerror callback is not provided, eventual error will just throw. So no extra (not needed) promise is created and any error either returned by promise or thrown in success handler will be exposed, that's what you should expect.

In Q we will be adding this functionality as .done(), so as to better match WinJS and (sort of) jQuery. .end() will become deprecated. It'd be cool if you wanted to join us in standardizing on that API!

Mariusz Nowak

unread,
Oct 2, 2012, 9:36:53 AM10/2/12
to nod...@googlegroups.com
Domenic, I'll be very happy to join. Where exactly you're discussing it? Q mailing list?

Domenic Denicola

unread,
Oct 2, 2012, 9:51:29 AM10/2/12
to nod...@googlegroups.com
https://github.com/kriskowal/q/issues/86, although it's been dormant waiting for me to finish the branch

Jeff Barczewski

unread,
Oct 2, 2012, 11:52:44 AM10/2/12
to nod...@googlegroups.com
Mariusz,

Thanks for comparing with When and also mentioning performance characteristics! It really helps to have someone so familiar with the space to do such an eval since you know the details inside and out.

All the best,

Jeff

Jeff Barczewski

unread,
Oct 2, 2012, 12:01:03 PM10/2/12
to nod...@googlegroups.com
Mariusz,

I was using an old version of your deferred module, Defe...@0.1.1 which was API compatible with jQuery.

I guess for upgrading many things have changed since then:

 - .promise() becomes .promise
 - Deferred.when()  - I will have to create my own, right?

Anything else that you remember which will need to change? (I know this was a long time ago when you changed it so not a big deal if you don't remember)


If I want to keep the jQuery.Deferred style API (for consistency on all client and server code) but use your latest module, then I guess I need to create some light adapters which will use your api under the covers.

If you have any other ideas or thoughts on this let me know.

Thanks in advance!

Jeff


Mariusz Nowak

unread,
Oct 2, 2012, 12:30:27 PM10/2/12
to nod...@googlegroups.com
Jeff, I think you're talking about different implementation: https://npmjs.org/package/Deferred it's still at v0.1.1 and in fact it resembles jQuery's Deferred. It was published later on npm with same name but uppercased (Today npm wouldn't allow that ;)

For Deferred (one that opened this topic) changes are carefully described in https://github.com/medikoo/deferred/blob/master/CHANGES v0.1 -> v0.6 might be a long read ;)

Answering your questions:

On Tuesday, October 2, 2012 6:01:03 PM UTC+2, Jeff Barczewski wrote:

 - .promise() becomes .promise
 

Exactly .promise returns something conceptually same as .promise() in jQuery's version

>  - Deferred.when()  - I will have to create my own, right?

There's no when in this Deferred implementation. Same can be achieved with:

deferred(promiseOrValue).then(onsuccess, onerror);

 
Anything else that you remember which will need to change? (I know this was a long time ago when you changed it so not a big deal if you don't remember)


'fail', 'always' and 'done' can be achieved with promise.aside: https://github.com/medikoo/deferred#aside

In general Deferred (v0.6) implementation provides solutions to many other use cases, so it has much more to offer. jQuery's implementation is very basic, it evolved on client-side where asynchronous programming is not as complex as in Node.js

Cheers!
Mariusz

Jeff Barczewski

unread,
Oct 2, 2012, 12:57:39 PM10/2/12
to nod...@googlegroups.com
Mariusz,

Ah, I didn't catch that it was a different package, I only remembered the name and thought that you switched from capital D to lowercase deferred in later version. (I'm glad npm doesn't allow conflicts like that any more, is just confusing).

Thanks for pointing out that it was really a different package.

I appreciate the info on what I would change to switch though. 

All the best,

Jeff

Domenic Denicola

unread,
Oct 2, 2012, 10:18:42 PM10/2/12
to nod...@googlegroups.com
jQuery promises also aren't interoperable with other promise-consuming libraries, because they do not transform errors thrown in callbacks into rejection reasons, violating Promises/A. So e.g.

function doOperationAndDontGiveUp(operation) {
    return operation().then(null, function (error) {
        if (error instanceof TemporaryNetworkError) {
            return doOperationAndDontGiveUp(operation);
        }
        throw error;
    });
}

doOperationAndDontGiveUp(whatever).then(console.log, function (error) {
   console.log("A non-temporary error: ", error);
});

will not work, because you can't re-throw errors in jQuery to stay in a rejected state; the error will end up uncaught by any handlers except window.onerror/process.on("uncaughtException"), and so the rejection handler will never be called.

There's also the issue where jQuery promises can be resolved with multiple values, which other libraries cannot consume.

You're better off using a different promise library, like Kris Kowal and myself's Q, or Mariusz's deferred, or When, in both the browser and the server, and immediately converting any jQuery promises to real Promises/A promises using e.g. Q.when(jQueryPromise).

Domenic Denicola

unread,
Oct 2, 2012, 10:28:51 PM10/2/12
to nod...@googlegroups.com
I forgot to add, this is especially important in Node, where promises solve the entire uncaught-exception-in-callbacks problem if used consistently throughout an app. An exception in a very deeply-nested promise callback simply transforms that innermost promise into a rejected promise, which then propagates up the chain to the closest rejection handler (just like sync exceptions!). This is unlike the situation with callbacks, where an exception in a deeply-nested callback jumps immediately out to process.on("uncaughtException").

Notably this uncaught exception jumping is the problem domains attempt to solve; they do so in the majority of cases, when all your asynchronicity eventually comes from the EventEmitters that domains hook into, at the cost of introducing an abstraction that---unlike promises---has no parallel to sync code.

But yeah, since jQuery doesn't translate exceptions into rejections, this entire benefit is lost and you're back in traditional callback land, where an exception inside a callback jumps all the way back up to window.onerror.

Jake Verbaten

unread,
Oct 2, 2012, 11:43:45 PM10/2/12
to nod...@googlegroups.com
domains have a parallel to sync code. Use event emitters for your sync flow. promises only have a parallel to sync code if you use promises for sync & async code.

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

Mariusz Nowak

unread,
Oct 3, 2012, 3:42:11 AM10/3/12
to nod...@googlegroups.com
Didn't know about that one, that's indeed big flaw, jQuery's solution is far from complete.

Domenic Denicola

unread,
Oct 3, 2012, 9:26:01 AM10/3/12
to nod...@googlegroups.com
On Tuesday, October 2, 2012 11:44:23 PM UTC-4, Raynos wrote:
domains have a parallel to sync code. Use event emitters for your sync flow. promises only have a parallel to sync code if you use promises for sync & async code.

By "sync code" I meant code with functions that return values and throw exceptions. (One might call this "normal code" :P.) Sure, you can do everything with (sync) event emitters, but you lose guarantees like only one return value, only one thrown exception, and never both from the same function. You cannot make assumptions like that about your code's flow if it's entirely structured through event emitters, which makes things harder on the programmer---you can no longer focus on just getting things done but instead have to worry about the technology you're using to do it. And of course you have to rebuild all of the sync mechanisms like function composition (obviously easy if your EEs are streams), exception bubbling (harder, e.g. domains are top-down while exceptions bubble bottom-up), catch clauses, finally clauses, etc.

Not sure what you mean by the last sentence. What I meant by saying promises parallel sync code is that there is an exact correlation between promise concepts and sync code concepts, e.g. fulfillment value <-> return value, rejection reason <-> thrown exception, rejection propagation <-> exception bubbling, fulfillment value transformation <-> functional composition, rejection transformation <-> catch clauses, and so on. You should never use promises for sync code IMO.

Mikeal Rogers

unread,
Oct 4, 2012, 1:55:15 AM10/4/12
to nod...@googlegroups.com
I agree with the principal that you shouldn't shove all your control flow through event emitters, but I'd take it a step further, and say you should shove it all through *any* single abstraction.

We have event emitters, streams, callbacks and "normal" (sync) code at play in all node programs. each of those abstractions do one thing well, and don't do what the others do well. Domains work OOTB on all event emitters but have some complications with callbacks and streams.

Streams that are long running (like the redis client) don't get scoped to the right domain since they are created early and never destroyed.

Callbacks are not automatically bound to a domain, so any "normal" code in them that throws will not be caught by the domain.

At Summer Camp we discussed the best way to handle this problem. The solution seemed to be that module authors, like the redis client, should not scope their stream to a domain but instead must bind any callbacks passed to them to process.domain if one exists. I would say the same should go for libraries like async and any other library that "manages callbacks"

This is a very long way of getting at what I think is the most important part of flow control discussions after 0.8, that regardless of the abstractions you use everyone needs to find a way to attach their errors to the "active" domain if they want to be compatible with error handling in node.js. Just like a promise library needs to expose something compatible with the callback interface to get at APIs in core it'll need to check for process.domain and bind it's success callbacks and error handling to it.

I know that part of the value proposition of these libraries has always been that they have their own error handling mechanisms but the reality is that once frameworks wrap their user's handles in a domain all the nice error handling logic of the framework will be attached to the domain and unless you want to make your users integrate all their promises in to the framework by hand you'll need to work with the active domain. The upside is that people using these frameworks will have an easier integration path with your promise library (as in, it'll just work).

-Mikeal

Mariusz Nowak

unread,
Oct 4, 2012, 3:27:30 AM10/4/12
to nod...@googlegroups.com
Mikeal, thanks, that's a very valuable guideline. I haven't yet look deeply into domains, now I definitely would.

Domenic Denicola

unread,
Oct 4, 2012, 3:39:43 AM10/4/12
to nod...@googlegroups.com

On Thursday, October 4, 2012 7:55:50 AM UTC+2, Mikeal Rogers wrote:
Domains work OOTB on all event emitters but have some complications with callbacks and streams.

Streams that are long running (like the redis client) don't get scoped to the right domain since they are created early and never destroyed.

Callbacks are not automatically bound to a domain, so any "normal" code in them that throws will not be caught by the domain.

At Summer Camp we discussed the best way to handle this problem. The solution seemed to be that module authors, like the redis client, should not scope their stream to a domain but instead must bind any callbacks passed to them to process.domain if one exists. I would say the same should go for libraries like async and any other library that "manages callbacks"

This is a very long way of getting at what I think is the most important part of flow control discussions after 0.8, that regardless of the abstractions you use everyone needs to find a way to attach their errors to the "active" domain if they want to be compatible with error handling in node.js. Just like a promise library needs to expose something compatible with the callback interface to get at APIs in core it'll need to check for process.domain and bind it's success callbacks and error handling to it.

I would love to play nicely with domains, but I'm not sure it's as straightforward as this. Promise libraries wrap all callbacks in a try/catch, storing any errors for potential future listeners, so there are no truly unhandled exceptions---simply ones that nobody has listened for, yet. So I'm not sure how you would surface those to a domain, or if it's even appropriate to do so. Guidance definitely appreciated.

There's also the larger point, promises entirely aside, of whether domains are truly the future of error handling in Node. There has been no evangelism, or tutorials, and from what I've seen, very little adoption. So far they simply seem like the core team/illuminati's favorite error handling library, which just so happens to be distributed with node instead of through npm. I'd love it if everyone was agreed on a single solution, but the impression I got from summer camp was that the only people using domains are those who are (a) close to the core team, and (b) write applications, not just hack on node core itself.

As I said, I want domains to succeed, even if only from the probably-in-your-eyes-misguided motivation that I like having standards handed down to me that we can all agree on, whether it be ES6 or domains. But I just don't see any effort or movement in that direction.

Mariusz Nowak

unread,
Oct 4, 2012, 3:47:08 AM10/4/12
to nod...@googlegroups.com


On Thursday, October 4, 2012 9:39:43 AM UTC+2, Domenic Denicola wrote:

On Thursday, October 4, 2012 7:55:50 AM UTC+2, Mikeal Rogers wrote:
Domains work OOTB on all event emitters but have some complications with callbacks and streams.

Streams that are long running (like the redis client) don't get scoped to the right domain since they are created early and never destroyed.

Callbacks are not automatically bound to a domain, so any "normal" code in them that throws will not be caught by the domain.

At Summer Camp we discussed the best way to handle this problem. The solution seemed to be that module authors, like the redis client, should not scope their stream to a domain but instead must bind any callbacks passed to them to process.domain if one exists. I would say the same should go for libraries like async and any other library that "manages callbacks"

This is a very long way of getting at what I think is the most important part of flow control discussions after 0.8, that regardless of the abstractions you use everyone needs to find a way to attach their errors to the "active" domain if they want to be compatible with error handling in node.js. Just like a promise library needs to expose something compatible with the callback interface to get at APIs in core it'll need to check for process.domain and bind it's success callbacks and error handling to it.

I would love to play nicely with domains, but I'm not sure it's as straightforward as this. Promise libraries wrap all callbacks in a try/catch, storing any errors for potential future listeners, so there are no truly unhandled exceptions---simply ones that nobody has listened for, yet. So I'm not sure how you would surface those to a domain, or if it's even appropriate to do so. Guidance definitely appreciated.


Domenic, I think it's only about unhandled errors, so in fact errors thrown by 'end', they probably should be covered by domains in Node. I would definitely not incorporate domain logic into 'then' error handling.

Domenic Denicola

unread,
Oct 4, 2012, 3:50:40 AM10/4/12
to nod...@googlegroups.com

Ah, that makes sense, thanks. In which case, I think Q is already OK, since we throw the error inside `process.nextTick` which IIRC means it will be automatically captured by the active domain.

Mikeal Rogers

unread,
Oct 4, 2012, 3:55:13 AM10/4/12
to nod...@googlegroups.com
actually, it won't :(

Mikeal Rogers

unread,
Oct 4, 2012, 4:07:11 AM10/4/12
to nod...@googlegroups.com
I totally understand what you're saying but I think there are a few things you're missing.

The problem we had with errors in the node implementation of promises, which was not great, was that errors were often unhandled because it was extra typing to write a handler for them. This lead to "silent" failures, which were terrible. This is why many better promise implementations throw entirely unhandled errors.

Q (and i'll assume deferred and make broad generalizations about promises from here on out) has some amount of error propagation to make writing handlers for errors easier, and so that they can live in less places, and Q should not replace that with domains. BUT, if after propagation there is no handler it should emit error on the domain (if it exists).

Now, in places where Q takes a success callback and then wraps that in a try/catch, it should also wrap code in the try statement in an enter/exit for the current domain. A throw will still be caught by your try/catch because domains don't do try/catch they wait for it to bubble up to uncaughtException, but making sure your user's code runs in the same domain they entered the promise with will mean that any event emitters or API calls (in libraries by authors that are domain aware) can also get attached.

I *think* this is the right way to support domains in libraries like this. For good measure, I had a go at adding domains to async [ https://github.com/caolan/async/pull/185 ] to make sure that most of these ideas play out in real code. I'm also attempting to add them to node_redis but it's a little harder than I anticipated, the node_redis code is a bit obfuscated by insane amounts of optimization :)

-Mikeal

Domenic Denicola

unread,
Oct 4, 2012, 4:25:08 AM10/4/12
to nod...@googlegroups.com


On Thursday, October 4, 2012 10:07:31 AM UTC+2, Mikeal Rogers wrote:
I totally understand what you're saying but I think there are a few things you're missing.

The problem we had with errors in the node implementation of promises, which was not great, was that errors were often unhandled because it was extra typing to write a handler for them. This lead to "silent" failures, which were terrible. This is why many better promise implementations throw entirely unhandled errors.

Q (and i'll assume deferred and make broad generalizations about promises from here on out) has some amount of error propagation to make writing handlers for errors easier, and so that they can live in less places, and Q should not replace that with domains. BUT, if after propagation there is no handler it should emit error on the domain (if it exists).

Yes, this is something promise libraries still struggle with. The problem is, someone may perform an async operation, and not care about the result for some amount of time. The idea is you should be able to pass the rejected promise through multiple levels of your program before someone eventually decides they care and attaches a failure handler. There's no way to predict when or if that will happen though :(.

The best we have so far is that you should always either (a) `return` the promise so it's now someone else's responsibility, or (b) signal that you're done, and not going to handle any errors, via extra typing (the `.end()` Marisz references). Sigh, the dark underbelly of promise-utopia comes to light... :P
 
Now, in places where Q takes a success callback and then wraps that in a try/catch, it should also wrap code in the try statement in an enter/exit for the current domain. A throw will still be caught by your try/catch because domains don't do try/catch they wait for it to bubble up to uncaughtException, but making sure your user's code runs in the same domain they entered the promise with will mean that any event emitters or API calls (in libraries by authors that are domain aware) can also get attached.

This makes a lot of sense, good call!

Behrad Zari

unread,
Jul 8, 2014, 3:37:52 AM7/8/14
to nod...@googlegroups.com
and whats the best decision when composing an ROBUST ACK-based callback-based IOC container which call's user's callback and at the end should be notified with the passed done callback, providing that the container wants to guard against user's code probable un-handled errors (which will cause no done callback be called later, and container won't get notified) and also not to swallow un-handled errors in user's code context?

if should use domains (to guard against all type of errors), then
1) If user has not any active domains, we provide a domain, catch errors, re-emit them on an EE so that user can know about it!?
2) What if user had an active domain? How can we chain/propagate errors between different domains so that both container and user can do their handlings?

I've seen libs like https://github.com/epeli/qdomain and https://github.com/CrabDude/trycatch but can't yet get the benefits from mixing promises with domains, I found no other solutions for catching all types of errors with promises without domains.
Reply all
Reply to author
Forward
0 new messages