dojo.Deferred Magic
A quick look at the magic of dojo.Deferred.
I don’t know why it took me so long to grasp dojo.Deferred. But now that I have spent some time on it, I think I’ll be using it everywhere.
In previous implementations of asynchronous functions I would either pass into the asynchronous call a callback, or I would change the xhr to synchronous. Both are obviously fraught with problems.
Deferred’s operate as filter chain. They therefore REQUIRE that you return a value that will be passed to the next callback. So that means there not 100% safe to pass around willy-nilly, since you may be relying on an unfiltered result. But there is a very easy workaround for that too.
So here in a nutshell is the magic:
- If you add a callback to a dojo.Deferred, it is guaranteed to get called unless there is an error. This means that regardless if your Deferred is already complete (i.e. your xhr request has already returned), if you add a callback to it 10 minutes later, it will still get called (immediately). That is brilliant, since it means you can code to a simple standard — with callbacks added to the Deferred’s chain.
- If you have a Deferred that you do not want to allow clients to filter, then don’t pass the deferred to it. Instead, create a dojo.DeferredList that is composed of your “protected” deferred, and then pass that back to the client. They can muck with their return value all they want, and won’t touch your original Deferred. But the any callbacks added to the DeferredList will still be guaranteed to be called when your “protected” deferred returns.
- One of the challenges of asynchronous programming for me in the past was this infinite propogation of callbacks, that I couldn’t tell what was going on. So I would forget about it, and use synchronous calls, and then switch back to a synchronous model of programming. But that certainly hurts the user experience. With Deferred, the flow of the program is much more evident, and easier to debug.
- You can actually trigger a callback only when multiple Deferred’s return using a DeferredList. This is slightly different in its syntax, in that you will get a set of return values passed to the callback — all the Deferred items in the original DeferredList, with each result’s location corresponding to their index in the original DeferredList array.
Everybody likes to see some code, so here is an example of the magic:
dojo.require("dojo.DeferredList");
document.write("the first thing you should see is callback1 getting called with content = CONTENT ");
var def = new dojo.Deferred();
def.addCallback(function(content) {
document.write("callback: 1, content:" + content + "");
return content + "1";
})
def.callback("CONTENT");
document.write("the next thing you should see is callback2 getting called with content = CONTENT1 ");
def.addCallback(function(content) {
document.write("callback: 2, content:" + content + "");
return content + "2";
});
document.write("next, prove that a deferred list will give you the content (content12), but won't let you change the content");
var def2 = new dojo.DeferredList([def]);
def2.addCallback(function(content) {
document.write("callback:3, content:" + content + "");
return "SHOULD NEVER SEE THIS";
})
document.write("next, show callback4 with unchanged content (CONTENT12), despite callback3 changing it");
def.addCallback(function(content) {
document.write("callback:4, content:" + content + "");
return content + "3";
});
and the result should look like:
the first thing you should see is callback1 getting called with content = CONTENT
callback: 1, content:CONTENT
the next thing you should see is callback2 getting called with content = CONTENT1
callback: 2, content:CONTENT1
next, prove that a deferred list will give you the content (content12), but won’t let you change the content
callback:3, content:true,CONTENT12
next, show callback4 with unchanged content (CONTENT12), despite callback3 changing it
callback:4, content:CONTENT12