1. What's the motivation behind not wrapping errors with Error?
2. Chaining is really useful. Why would you not want to do it
automatically when a callback/errback returns a Deferred?
3. Cancellers are really useful. Twisted added them after six years,
and it has simplified the design of many other components. Maybe the
goog.async.Deferred cancellation behavior is not ideal, in which case
better behavior should probably be stolen from Twisted.
Ivan
Right, cancellation is not always an error, and your canceller can
trigger the callback chain if it wants to. The implementation of
cancel in deferred.js does this after calling the canceller:
if (!this.hasFired()) {
this.errback(new goog.async.Deferred.CancelledError(this));
}
Is triggering the callback chain in the canceller enough to do what
you want? Not calling either chain would break the guarantees that
Deferreds provide, and I think it would make it harder to compose
Deferreds. I can talk more about this, but there people far more
qualified.
I'll point to these resources for Deferred design, in case anyone is interested:
1. The Twisted source code, which uses Deferreds in hundreds of
places, including some with cancellers.
2. Twisted's twisted/internet/defer.py, especially before
http://twistedmatrix.com/trac/ticket/411 was merged (which complicated
the code slightly).
3. #twisted on irc.freenode.net, which will happily talk about
Deferred design in general.
4. http://twistedmatrix.com/trac/ticket/990 , a general discussion of
how cancellers should work. It's a bit hard to wade through.
I'm not saying everything should work like their implementation, but
they have been thinking about the problem for 8 years, so it's wise to
leverage their output.
Ivan
Forgot the mention the paper that introduced the Deferred,
"Generalization of Deferred Execution in Python":
http://www6.uniovi.es/python/pycon/papers/deferex/
as well as the modern documentation:
http://twistedmatrix.com/documents/10.2.0/core/howto/defer.html
http://ezyang.com/twisted/defer2.html
Ivan
I agree. An Error subclass like this would be a good thing to have in
Closure Library.
> (4) I think the contract of a Deferred is stronger if there is no
> cancel() method. I had not noticed that it calls all of the errbacks
> (until Garry pointed it out) because that is not mentioned in the
> JSDoc, either. Though Shawn, I have to disagree with you on this one
> because I think the one good thing that cancel() does is call all of
> the errbacks; otherwise, I think it would be too easy to get a memory
> leak, no?
I don't think cancel() makes the contract any weaker. Cancellers
simply allow some code to suggest "stop doing stuff if possible",
which stops useless operations (because the result is no longer
needed). The Deferred still must go through either the callback or
errback chain.
FWIW, one way to think about Deferred features is to think of the
synchronous code analog. addErrback is like try {} catch {}. addBoth
is like try {} finally {}. A canceller in synchronous code would be
"if someone_stopped_me_early: abort()".
Ivan
The current implementation is a branched and modified version of
Dojo's [1], which was based on MochiKit [2], which in turn was
inspired by Twisted [3]. These also informed the versions in jQuery
[4].
I'm not particularly fond of the current API -- the methods and names
are not intuitive ("addErrback", "addCallback" vs "addCallbacks",
etc), and it dispenses with patterns used elsewhere in Closure. At
some level, it's reimplementing observer/eventTarget, and I wonder if
it should just be one. Also, why is it not Disposable? It's a
powerful concept, but for it to be used widely, I'd want a
well-written basic class, which this isn't. Also, I've had some
painful experiences working with this version -- swallowing of runtime
errors being the worst of them.
I agree with Bolin on the confusion or returning a callback as a
special case. It conflates the two, and I'd rather chaining be more
explicit. What would help in all of these is sample usages of the new
API. I'd also like to see better tools for working with asynchronous
actions.
Finally, I am fond of jQuery's usage of "Promises" as a subset of
Deferreds, and this is a good candidate for a subclass -- read-only
views of futures. Most things should be returning "promises" that
can't be ended by outside code.
http://en.wikipedia.org/wiki/Futures_and_promises#Read-only_views
Nathan
[1] http://dojotoolkit.org/api/1.3/dojo/Deferred
[2] http://mochi.github.com/mochikit/doc/html/MochiKit/Async.html#fn-deferred
[3] http://twistedmatrix.com/documents/10.1.0/api/twisted.internet.defer.Deferred.html
[4] http://api.jquery.com/category/deferred-object/
I've been using Deferred heavily recently, and I'm overall quite fond of the API. The error hiding is a problem, but I've found the rest of it to work quite well. I use it in very different circumstances from those in which I use events: a Deferred is a value that will materialize at some point in the future, whereas events are notifications that something has happened. The Deferred-specific functionality of chaining values, branching, cancelling, and sending errors through the chain has been very valuable to me.I would be saddened if chaining deferreds became substantially more verbose than it is today. I value the fluidity of being able to saydeferred.addCallback(function(val) { return val.foo(); }).addCallback(function(val) { return val.bar(); })....
Right. "Materialization" of the value (or failure to materialize) is
the event in question.
I wouldn't want to impose a more-verbose usage, and it's probably
worth making shorthands for "attach a listener, pull the result, and
pass it to the next in line" because it's such a common operation.
jQuery calls these "filters" for use with "pipe()", or, as Garry
suggested "process".
Nathan