Deferred chains?

62 views
Skip to first unread message

Doug Crawford

unread,
Mar 21, 2006, 5:45:56 PM3/21/06
to MochiKit
Does anyone have an example of how to setup or use Deferred Chains
where a callback returns another Deferred instance. I am having
trouble understanding what is supposed to happen with any return value
from a Deferred callback function. Is this return value used as the
first calling argument for subsequent callback functions?

Bob Ippolito

unread,
Mar 21, 2006, 5:56:06 PM3/21/06
to Doug Crawford, MochiKit
There's nothing magic about them.  Just play with them in the interpreter and read the docs...  The code isn't very complicated either.

>>> d = new Deferred();
Deferred(1, unfired)
>>> e = new Deferred();
Deferred(2, unfired)
>>> d.addCallback(function () { return e; })
Deferred(1, unfired)
>>> d.addCallback(writeln)
Deferred(1, unfired)
>>> d.callback(1)
>>> e.callback("write this")
write this
>>> d
Deferred(1, success)
>>> e
Deferred(2, success)

Doug Crawford

unread,
Mar 21, 2006, 7:36:44 PM3/21/06
to MochiKit
Thanks, I am starting to understand this stuff but not completely. In
your example you never explicitly call e.addCallback() so when you call
e.callback("write this") how does the e object choose the right
callback function?

With multiple callbacks are attached to the same Deferred object the
first callback will be called with the argument(s) supplied to
Deferred.callback(). Then subsequent callbacks will be called with the
result of the previous callback. So according to your example the
second callback on d (which is writeln) will be called with the result
from the previous callback (which is the e object). Is this correct?

I thought I could restructure the example slightly and still get the
same end result but the following example does not print "write this".
I am sure it is obvious why this similar example does not work, but I
don't see it.
>>> d=new Deferred
Deferred(1, unfired)
>>> e=new Deferred
Deferred(2, unfired)
>>> d.addCallback(writeln)
Deferred(1, unfired)
>>> e.callback("write this")
>>> d.callback(e)
Deferred(2, success)

Bob Ippolito

unread,
Mar 21, 2006, 8:24:49 PM3/21/06
to Doug Crawford, MochiKit
On Mar 21, 2006, at 4:36 PM, Doug Crawford wrote:


Thanks,  I am starting to understand this stuff but not completely.  In
your example you never explicitly call e.addCallback() so when you call
e.callback("write this") how does the e object choose the right
callback function?

When a Deferred ends up in the callback chain, the addCallback is implicit.  Deferreds are never passed to callback functions, only the value passed to callback or errback is.

With multiple callbacks are attached to the same Deferred object the
first callback will be called with the argument(s) supplied to
Deferred.callback().  Then subsequent callbacks will be called with the
result of the previous callback.  So according to your example the
second callback on d (which is writeln) will be called with the result
from the previous callback (which is the e object).  Is this correct?

The result from the previous callback is not the e object, because e is a Deferred.  Callbacks can receive anything but Error and Deferred; Errbacks can only receive Error instances.  If a Deferred is the result of a callback or errback function, then it's chained.  It simply wouldn't work otherwise, because you have to return something, and the callbacks/errbacks don't even have a reference to the Deferred instance that they are attached to.

Basically everything a Deferred does happens in one method: Deferred.prototype._fire.  It's about a page of code.  If you're really trying to understand Deferred, then the implementation would probably help it "click".

Effectively here's what happens (I've replaced the reprs with a description of what happened):
>>> d = new Deferred();
Created a Deferred
>>> e = new Deferred();
Created a second Deferred
>>> d.addCallback(function () { return e; })
Populated the callback chain of d with a Deferred-returning function
>>> d.addCallback(writeln)
Added another callback to the callback chain of d.
d's callback chain looks like this now:
[return a Deferred, write something]
>>> d.callback(1)
Start processing d with a callback value of 1.
is now considered fired, and the first callback in its callback chain starts.
The first callback ignores the value and returns a Deferred (e).
e is not fired yet, so it has no result to pass through d's callback chain.
d is now paused, and a callback/errback pair is added to e that will unpause and continue d.
>>> e.callback("write this")
Start processing e with a callback value of "write this".
e is now considered fired, and the first callback in its callback chain starts.
The first and only callback in this chain was the implicit continue of d.
This callback is called, which continues d's processing chain with a value of "write this"
The writeln callback is called with the value "write this" and d's callback chain is now empty.

I thought I could restructure the example slightly and still get the
same end result but the following example does not print "write this".
I am sure it is obvious why this similar example does not work, but I
don't see it.
d=new Deferred
Deferred(1, unfired)
e=new Deferred
Deferred(2, unfired)
d.addCallback(writeln)
Deferred(1, unfired)
e.callback("write this")
d.callback(e)
Deferred(2, success)

It's an error to pass a Deferred object to callback directly.  Deferreds can only be used in the result of a callback function.  I'll either have to document this restriction or change the code so that it works.  I'll probably document the restriction because it's highly unlikely to be a useful feature (code that does this is almost always incorrect).

The reason for this is that the creator of the Deferred is responsible for the callback.  If the callback is dependent on some other Deferred, usually the correct code would be to simply return that Deferred directly instead of creating a new one.  Additionally, it is an error to add callbacks to a Deferred after it has been chained (the callback value isn't preserved anyway, so if you tried it bad things would happen).

I guess the best solution would be to add some checking to Deferreds so that these situations cause errors to be thrown instead of the current silent treatment.

-bob

Bob Ippolito

unread,
Mar 21, 2006, 9:23:24 PM3/21/06
to MochiKit, Doug Crawford

Ok, I've revised the implementation of Deferred to do this sanity
checking, and have added notes to the documentation for callback/
errback/addCallback/etc. that point out those edge cases.

-bob

Doug Crawford

unread,
Mar 21, 2006, 10:45:17 PM3/21/06
to MochiKit
Thanks, your annotated interpreter session example really helped. The
additions to the documentaion are also very helpful.

Reply all
Reply to author
Forward
0 new messages