--
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.
Rakesh,
Yip, I tried explaining this in my previous message but your
> the ability to attach handlers to the deferred even after the event has occurred
explanation is better. It's an awesome feature.
--
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.
--
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.
--
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.
--
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.
--
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.
To unsubscribe from this group, send email to nodejs+un...@googlegroups.com.
[1] http://groups.google.com/group/commonjs/browse_thread/thread/e93f73ef97e88439/967367b7826aaf04
[2] http://wiki.commonjs.org/wiki/Promises
[3] http://github.com/280north/narwhal/blob/master/lib/promise.js
Kris
On Dec 11, 5:04 pm, Felix Geisendörfer <fe...@debuggable.com> wrote:
> Heya,
>
> why do today what you can do tomorrow?
>
> Currently node.js implements a simple & elegant system for dealing
> with non-blocking operations, the Promise API. You request an
> operation to be performed, and you get a promise object which allows
> you to track the fulfillment of this operation by specifying one or
> more callbacks / error handlers.
>
> As neat as this concept is, anybody familiar with it will have noticed
> its shortcomings:
>
> Promises are hard to build upon. If you want to provide a high-level
> wrapper for some low-level Promise-based API, right now you are stuck
> with creating a second promise and delegating / processing the
> original promise events by hand.
>
> Exception handling is manual. Promises may fail in unexpected ways
> causing the main event loop to crash and node to exit. You can defend
> against this by manually wrapping all of your logic in try..catch
> blocks, but you are out of luck if the issue exists in a 3rd party
> library. The last line of defense, process.addListener
> ('uncaughtException'), is suitable to keep your process from crashing,
> but any kind of failure recovery logic is rather hard to implement on
> top of it.
>
> Promises are hard to chain. While we might be able to do everything in
> parallel with node, there are tons of use cases that require certain
> operations to be performed in a certain sequence. Using the current
> promise API, this will usually lead to very deep nesting and high
> cyclomatic complexity.
>
> Given the critical role that Promises have in the current node.js API,
> it seems only fair to make them capable enough to deal with most of
> the issues that first time users as well as power users will end up
> struggling with. Luckily, there is some great prior API in this field,
> namely the Deferred API as, AFAIK, first seen in Twisted. Most of my
> knowledge about it however, is drawn from the Deferred implementation
> in Dojo.
>
> So, I have spend some time porting Dojo Deferreds to node.js, and I
> think they would be an excellent replacement for the current Promise
> API. For those not familar with Deferreds, I highly recommend the
> documentation of the dojo implementation as a first introduction:
>
> http://api.dojotoolkit.org/jsdoc/1.2/dojo.Deferred
>
> Basically, they are just like Promises with a few differences:
>
> Callback return values are important. The value returned from a
> callback is the value passed to the next callback. This makes it easy
> to build upon Deferreds by adding callbacks that manipulate the
> initial results.
>
> Exceptions are automatically handled. I actually propose a slight API
> change here, but let me explain. The default behavior of Deferreds is
> that any callback invocation is wrapped in a try..catch blok. If you
> do something stupid in your callback, the Deferred automatically
> switched its state to "error" and continues by executing all "errback"
> callbacks. What I like about this, is that you can rest assured that
> 3rd party Deferreds may never crash your application, but rather
> invoke a "errback" callback instead. However, I think this behavior
> should be disabled by default. My initial experience has been that it
> is too easy to oversee exceptions that are being thrown, which is very
> bad during development. Therefor my current port of dojo Deferreds
> diables this behavior by default. If you want to use it, you just
> call .trycatch() on any Deferred you receive to enable it
> transparently.
>
> Last but not least, Deferreds are very easy to chain in sequence. Any
> callback may return a new Deferred itself, which will cause any
> remaining callbacks to be put on hold until that Deferred has
> completed. This makes it trivial to express sequences of asynchronous
> operations.
>
> My current port of (dojo) Deferreds can be seen here:
>
> http://github.com/felixge/node-deferred/
>
> At this point it is essentially identical to dojo Deferreds, except
> that I removed all dependencies to dojo and made the mentioned change
> to the try..catch behavior.
>
> I herby would like to ask all of you to comment on replacing node.js
> Promises with Deferreds. It certainly would make working with async
> operations easier, and backwards compatibility is pretty good for most
> parts.
>
> If nobody sees major issues, I would volunteer to provide the
> necessary patch for node.js.
>
> -- Felix Geisendörfer aka the_undefined
>
> PS: I am also working on syntax sugar and concepts that make it easier
> to aggregate the results from multiple deferreds running in parallel
> or sequence, but those are not quite ready yet and would only
> complicate the discussion.
Dojo style:
var promise = asyncAction();
var promisePlusTwo = promise.addCallback(function(value){return value
+ 2});
promise.addCallback(function(value){print("value: " + value)});
promisePlusTwo.addCallback(function(value){print("value plus two: " +
value)});
might print:
value: 7
value plus two: 7
The side-effects of mutating promises has been bitten Dojo badly too,
with earlier versions, if you didn't return a value in the callback,
then the return value (undefined) would be assigned to the value of
the promise, and future callbacks would get undefined as their passed
in value. We now have a magic check that avoids modifying the Deferred
for undefined return values; magic that shouldn't be necessary.
Mutating promises are bad for object capability systems as well, where
one should be able to pass a promise to untrusted code without
worrying about what it will do with it. Dojo's Deferreds can't be
passed to untrusted if you are adding future callbacks that need to
reliably received values.
In regards to try/catching callbacks, the reason this has to be done
is in case a future callback handler provides the error handler, they
should be able to do so and reliably catch the errors. However, the
problem with errors silently being caught is definitely very
frustrating, and I think the best solution is to have a timeout for
how long an unhandled promise error can go without being handled (by
an error handler callback, and errback in Deferred's terms).
> CommonJS style:
> var promise = asyncAction();
> var promisePlusTwo = promise.then(function(value){return value + 2});
> promise.then(function(value){print("value: " + value)});
> promisePlusTwo.then(function(value){print("value plus two: " +
> value)});
> might print:
> value: 5
> value plus two: 7
What happens when value is an object?
var promise = asyncAction();
var promisePlusTwo = promise.then(function(value) {
value.number += 2;
return value;
});
var promise.then(function(value) { sys.p(value); });
promisePlusTwo.then(function(value) { sys.puts('value plus two:');
sys.p(value); });
// outputs what?
What have done is taking a super simple API:
var deferred = new Deferred();
deferred.addCallback(func1).addCallback(func2);
deferred.addCallback(func3);
deferred.addCallback(func4);
Which is simple and intuitive. The functions will get called in this
order: func1, func2, func3, func4, and no matter what, they see the
results of the deferred (albeit maybe modified) in that order. With
the old API all the functions were called in the order that they were
added. Simple stuff.
And made it more ambiguous:
var promiseStart = new Promise();
var promise1 = promiseStart.then(func1);
var promise2 = promiseStart.then(func2);
promise1.then(func3);
promise2.then(func4);
promiseStart.emit(value);
This is called (I assume) in this order: func1, func3, func2, func4.
And it would seem like func2 should see exactly the value that either
promiseStart or func1 emits, but func3 has an opportunity to affect
this even though func2 is added to a different promise.
And then what happens to the course of action if I change the order around some?
var promiseStart = new Promise();
var promise1 = promiseStart.then(func1);
promise1.then(func3);
var promise2 = promiseStart.then(func2);
promise2.then(func4);
promiseStart.emit(value);
It seems like you have taken a simple chain:
func1 --> func2 --> func3 --> func4
and made it a tree:
func1 - --> func2
\---> func3 \---> func4
(I'm sorry if that gets mangled in email formatting).
And from that diagram I don't think func2 should be allowed to affect
the value func3 receives. And if it isn't allowed then you have to
deep copy objects which for simple cases would be easy but could be
quite costly for large objects. And if it is allowed then you have to
think about the value modification in breadth-first versus depth-first
terms which sounds like a headache.
I can see the benefits that you have mentioned about with untrusted
code but at this point I'm not convinced that this benefit outweighs
the added complexity and ambiguity.
> And the other
> obvious difference is spelling ("then" vs "addCallback").
I don't understand why the name change. It is already established in
both Dojo and Twisted as addCallback. Stick with what people know.
Plus, like the explicitness of this:
p.addCallback(callback);
p.addErrback(errback);
as opposed to this:
p.then(callback, errback);
That seems so much nicer. and this:
p.then(null, errback);
is just plain ugly.
But that isn't an important point since you are suggesting that
individual Promise implementations add their own "sugar" functions on
top of this. I still don't understand the name change though.
> The side-effects of mutating promises has been bitten Dojo badly too,
> with earlier versions, if you didn't return a value in the callback,
> then the return value (undefined) would be assigned to the value of
> the promise, and future callbacks would get undefined as their passed
> in value. We now have a magic check that avoids modifying the Deferred
> for undefined return values; magic that shouldn't be necessary.
I don't understand, I thought the Dojo API made it so if you didn't
return a value in the callback the previous value was passed to the
next callback. This seems pretty intuitive and not magical. Am I
missing something?
Further more the new Promise API seems to raise questions about this
exact issue. To quote from the proposal about the `then` function:
> This should return a new promise that is fulfilled when the given
> callback is finished. The value returned from the callback is the
> fulfillment value for the returned promise.
What if the callback doesn't return anything? What is used as the
fulfillment value of the returned Promise? Does the returned promise
get the fulfillment value from the first Promise?
In summary, I'm not convinced Node should adopt the CommonJS Promises
API. While I feel like Node should match CommonJS APIs where
appropriate, if the API doesn't feel right then Node should do what it
thinks is best (see this comment from Felix earlier in this thread:
http://groups.google.com/group/nodejs/msg/541bace648404044). And I
feel like the CommonJS Promise API is questionable enough to warrant
us experimenting with other possibilities.
Anyway, let me know if you see any issues with the update CommonJS
proposal, and thanks for the feedback.
In addition, if you are interested, perhaps it might be beneficial to
reuse/share the same module for promises that is used in Narwhal. I
know there is desire and effort going in to see more interoperability
between Narwhal and Node, that might be a good step. Of course, the
promise module could be updated to be more in line with expectations
of Node, including adding addCallback, addBoth, etc. functions on
promises. The promise module also provides a number of convenience
functions for working with promises, such as static promise handlers
that can handle normal values or promises (I use that very extensively
in my framework, Pintura).
Also, just to note, there were still a couple issues that we didn't
really decide about:
* Should errors thrown from callbacks be caught (and routed to the
error handler on the next promise)? - Originally Felix felt no, but it
seems we weren't sure. I specified that they should be in the CommonJS
proposal, since I think that is the most consistent with the
asynchronous form of function calls analogy, and it doesn't require
type checking return values from callbacks, but it seems reasonable
for promise implementors to provide a switch for this.
* Should calls to the callback handlers be put on the event queue or
executed immediately? - Currently unspecified, and I don't know for
sure what is best.
Kris
> And made it more ambiguous:
>
> var promiseStart = new Promise();
> var promise1 = promiseStart.then(func1);
> var promise2 = promiseStart.then(func2);
> promise1.then(func3);
> promise2.then(func4);
> promiseStart.emit(value);
after talking to Kris in #commonjs I believe your example could be
rewritten to:
promiseStart = new Promise();
promiseStart
.then(func1)
.then(func2)
.then(func3)
.then(func3);
promiseStart.emit(value);
So as far as I am concerned, we wouldn't be loosing any of the
convenience provided by dojo. To me the callback chaining is a way to
compose new functions out of several async functions (promises). The
idea of immutable promises does make a lot of sense to me and I do
think that's what we should do in node. I don't really like the "then"
API, but if an alias to it is all that's required to be CommonJS
compatible I don't see the harm.
So I'm inclined to give the CommonJS proposal a chance, but I'm still
waiting on some comments from ryan about my initial patch.
-- Felix Geisendörfer aka the_undefined
I agree that my example was convoluted and could be done in a better
way. But I still think there are unanswered issues when you change
promises from being a chain to being a tree. And if we are intending
Promises to be thought of and used as a chain then why are making them
a tree?
I think the idea of immutable promises makes a lot of sense. And when
I first read it I thought it was brilliant. But then I started working
through some examples and began to have questions.
Specifically the issue of different branches of the tree modifying the
same object. Am I getting hung up on something that doesn't matter?
Does no one else see an issue with this?
> Specifically the issue of different branches of the tree modifying the
> same object.
I can't think of a case where this would be an issue. If you want to
control sequence, use nesting. Otherwise make sure none of your
callbacks make any assumptions about the order in which they will
fire.
> Am I getting hung up on something that doesn't matter?
> Does no one else see an issue with this?
Not sure. I will have to play more with them myself. This stuff is
sufficiently complex to exceed my imagination at times ; ). If you
have come across problematic situations so far, I'd love to hear about
them.
-- Felix Geisendörfer aka the_undefined
IMO, this code looks like the programmer is actually explicitly
wanting tree-style separation of promises, assigning them to different
variables even. Forcing a linear chain seems like it would be going
against the will of programmer, especially since they could easily add
then the promise handlers in straight chain as Felix demonstrated.
I think it is helpful (and intuitive) to think of promise handlers as
asynchronous counterparts to synchronous computations, taking an input/
parameter(s) value and returning/outputting a value. If you wrote:
var promiseStart = new Promise();
var promisePlusTwo = promiseStart.then(addTwo);
promiseStart.fulfill(4);
This should be asynchronous counterpart to:
valueStart = 4;
valuePlusTwo = valueStart + 2; // or addTwo(valueStart);
If the evaluation of (valueStart + 2) resulted both valuePlusTwo *and*
valueStart being incremented by two, that seems like it would not be
beneficial. Of course, in JavaScript valueStart remains 4 in this
example, and it seems like the analogous promise should behave the
same way.
On Dec 19, 12:09 pm, Felix Geisendörfer <fe...@debuggable.com> wrote:
> My only concern with tree-style promise composition vs chain-style is
> memory/performance. But, node will probably not do a lot of this
> internally and it shouldn't be an issue when properly used in user
> land.
That it is a fair criticism, but the very nature of promises is to
model evaluations that will take some time. It seems very rare that
the construction of a promise (a pretty lean JS object) would be
significant cost in comparison to the typical operations that a
promise is being used for (DB queries, file I/O, network I/O, etc).
> I don't really like the "then"
FWIW, I used "then" as a compromise between Dojo's Deferred API and
the ref_send API as these were the two different promise APIs that
were suggested in CommonJS discussions (ironically I was arguing for
Dojo's API and Tyler Close was arguing for ref_send). However, having
starting using this API more, I have actually have come to really like
"then". Not only is shorter, but it actually seems more meaningful.
"addCallback" properly suggests that a callback is being added, but
doesn't bring much suggestion of the fact that the callback is
participating in the computation, it's return value being used as the
fulfillment for the next promise. "then" actually seems to do a better
job of indicating the more general role of the callback, taking a
input promise and returning an output promise as the next computation
in the data flow. Plus, I still think it reads nicely:
doSomethingAsync().then(doTheNextComputation)
Kris
Alright, I'm on board. My complaint was that you couldn't do this
(translated to Promises):
objectStart = { value: 4 };
objectPlusTwo = objectStart.value + 2;
// objectStart.value == 4 and objectPlusTwo.value ==6
But now that I see it like this I realize you can't do that in
javascript anyway. So, in the branch-able version of promises, if you
don't want the different branches stepping on each others toes, either
deep copy objects or use primitives like Numbers and Strings.
> Not only is shorter, but it actually seems more meaningful.
> "addCallback" properly suggests that a callback is being added, but
> doesn't bring much suggestion of the fact that the callback is
> participating in the computation, it's return value being used as the
> fulfillment for the next promise. "then" actually seems to do a better
> job of indicating the more general role of the callback, taking a
> input promise and returning an output promise as the next computation
Agreed.