JavaScript doesn't currently support the concept of threads, but
it does offer the ability to perform asynchronous requests via the
XMLHttpRequest
object and through
delays with the setTimeout
function. However, it doesn't take too many asynchronous calls running
around before matters get awfully confusing. Base provides a class
called Deferred
to help manage the
complexity often associated with the tedious implementation details of
asynchronous events. Like other abstractions, Deferred
s allow you to hide away tricky
logic and/or boilerplate into a nice, consistent interface.
If the value of a Deferred was described in one sentence, however, it would probably be that it enables you to treat all network I/O uniformly regardless of whether it is synchronous or asynchronous. Even if a Deferred is in flight, has failed, or finished successfully, the process for chaining callbacks and errbacks is the exact same. As you can imagine, this behavior significantly simplifies bookkeeping.
Tip
Dojo's implementation of a Deferred
is minimally adapted from
MochiKit's implementation, which in turn is inspired from Twisted's
implementation of the same. Some good background on MochiKit's
implementation is available at http://www.mochikit.com/doc/html/MochiKit/Async.html#fn-deferred.
Twisted's implementation of Deferred
s is available at http://twistedmatrix.com/projects/core/documentation/howto/defer.html.
Some key features of Deferred
s are that they allow you to chain together multiple callbacks and
errbacks (error-handling
routines) so they execute in a predictable sequential order, and
Deferred
s also allow you to
provide a canceling routine that you can use to cleanly abort
asynchronous requests. You may not have realized it at the time, but
all of those XHR functions you were introduced to earlier in the
chapter were returning Deferred
s,
although we didn't have an immediate need to dive into that just then.
In fact, all of the network input/output machinery in the toolkit use
and return Deferred
s because of
the flexibility they offer in managing the asynchronous activity that
results from network calls.
Before revisiting some of our earlier XHR efforts, take a look
at the following abstract example that directly exposes a Deferred
, which forms the basis for some of
the concepts that are coming up:
//Create a Deferred var d = new dojo.Deferred(/* Optional cancellation function goes here */); //Add a callback d.addCallback(function(response) { console.log("The answer is", response); return response; }); //Add another callback to be fired after the previous one d.addCallback(function(response) { console.log("Yes, indeed. The answer is", response); return response; }); //Add an errback just in case something goes wrong d.addErrback(function(response) { console.log("An error occurred", response); return response; }); //Could add more callbacks/errbacks as needed... /* Lots of calculations happen */ //Somewhere along the way, the callback chain gets started d.callback(46);
If you run the example in Firebug, you'd see the following output:
The answer is 46 Yes, indeed. The answer is 46
Before jumping into some more involved examples, you'll probably
want to see the API that a Deferred
exposes (Table 4-3).
Table 4-3. Deferred functions and properties
Be aware that a Deferred
may
be in an error state based on one or more combinations of three
distinct possibilities:
A callback or errback is passed a parameter that is an
Error
object.A callback or errback raises an exception.
A callback or errback returns a value that is an
Error
object.
Tip
Typical use cases normally do not involve the canceller
, silentlyCancelled
, and fired
properties of a Deferred
, which provide a reference to the
cancellation function, a means of determining if the Deferred
was cancelled but there was no
canceller method registered, and a means of determining if the
Deferred
status of the fired,
respectively. Values for fired include:
â1: No value yet (initial condition)
0: Successful execution of the callback chain
1: An error occurred
Let's get warmed up with a simple routine on the server that briefly pauses and then serves some content. (The pause is just a way of emphasizing the notion of asynchronous behavior.)
The complete CherryPy file that provides this functionality follows:
import cherrypy from time import sleep import os # a foo.html file will contain our Dojo code performing the XHR request # and that's all the following config directive is doing current_dir = os.getcwd() config = {'/foo.html' : { 'tools.staticfile.on' : True, 'tools.staticfile.filename' : os.path.join(current_dir, 'foo.html') } } class Content: # this is what actually serves up the content @cherrypy.expose def index(self): sleep(3) # purposefully add a 3 sec delay before responding return "Hello" # start up the web server and have it listen on 8080 cherrypy.quickstart(Content( ), '/', config=config)
Assuming that the CherryPy content is saved in a file called
hello.py, you'd simply type python hello.py
in a terminal to startup
the server. You should be able to verify that if you navigate to
http://127.0.0.1:8080/ that "Hello" appears on
your screen after a brief delay.
Once you have CherryPy up and running save the file below as foo.html and place it alongside the foo.py file you already have running. You should be able to navigate to http://127.0.0.1:8080/foo.html and have foo.html load up without any issues:
<html>
<head>
<title>Fun with Deferreds!</title>
<script type="text/javascript"
src="http://o.aolcdn.com/dojo/1.1/dojo/dojo.xd.js">
</script>
<script type="text/javascript">
dojo.addOnLoad(function( ) {
//Fire off an asynchronous request, which returns a Deferred
var d = dojo.xhrGet({
url: "http://localhost:8080",
timeout : 5000,
load : function(response, ioArgs) {
console.log("Load response is:", response);
console.log("Executing the callback chain now...");
return response;
},
error : function(response, ioArgs) {
console.log("Error!", response);
console.log("Executing the errback chain now...");
return response;
}
});
console.log("xhrGet fired. Waiting on callbacks or errbacks");
//Add some callbacks
d.addCallback(
function(result) {
console.log("Callback 1 says that the result is ", result);
return result;
}
);
d.addCallback(
function (result) {
console.log("Callback 2 says that the result is ", result);
return result;
}
);
//Add some errbacks
d.addErrback(
function(result) {
console.log("Errback 1 says that the result is ", result);
return result;
}
);
d.addErrback(
function(result) {
console.log("Errback 2 says that the result is ", result);
return result;
}
);
});
</script>
</head>
<body>
Check the Firebug console.
</body>
</html>
After running this example, you should see the following output in the Firebug console:
xhrGet fired. Waiting on callbacks or errbacks Load response is: Hello Executing the callback chain now... Callback 1 says that the result is Hello Callback 2 says that the result is Hello
The big takeaway from this example is that the Deferred
gives you a clean, consistent
interface for interacting with whatever happens to come back from
the xhrGet
, whether it is a
successful response or an error that needs to be handled.
You can adjust the timing values in the dojo.xhrGet
function to timeout in less
than the three seconds the server will take to respond to produce
an error if you want to see the errback chain fire. The errback
chain fires if something goes wrong in one of the callback
functions, so you could introduce an error in a callback function
to see the callback chain partially evaluate before kicking off
the errback chain.
Warning
Remember to return the value that is passed into callbacks
and errbacks so that the chains can execute the whole way
through. Inadvertently short-circuiting this behavior causes
bizarre results because it inadvertently stops the callback or
errback chain from executingânow you know why it is so important
to always remember and return a response in your load
and error
handlers for XHR
functions.
Figure 4-2
illustrates the basic flow of events for a Deferred
. One of the key points to take
away is that Deferred
s act
like chains.
Another great feature of a Deferred
is that you have a clean way of
canceling an asynchronous action before it completes. The
following refinement to our previous example illustrates both the
ability to cancel an in-flight request as well as "injecting" a
Deferred
into the load and
error handlers of the request:
<html> <head> <title>Fun with Deferreds!</title> <script type="text/javascript" src="http://o.aolcdn.com/dojo/1.1/dojo/dojo.xd.js"> </script> <script type="text/javascript"> dojo.addOnLoad(function( ) { var d = new dojo.Deferred; //Add some callbacks d.addCallback( function(result) { console.log("Callback 1 says that the result is ", result); return result; } ); d.addCallback( function (result) { console.log("Callback 2 says that the result is ", result); return result; } ); //Add some errbacks d.addErrback( function(result) { console.log("Errback 1 says that the result is ", result); return result; } ); d.addErrback( function(result) { console.log("Errback 2 says that the result is ", result); return result; } ); //Fire off an asynchronous request, which returns a Deferredrequest
= dojo.xhrGet({ url: "http://localhost:8080", timeout : 5000, load : function(response, ioArgs) { console.log("Load response is:", response); console.log("Executing the callback chain now..."); //inject our Deferred's callback chain d.callback(response, ioArgs); //allow the xhrGet's Deferred chain to continue.. return response; }, error : function(response, ioArgs) { console.log("Error!", response); console.log("Executing the errback chain now..."); //inject our Deferred's errback chain d.errback(response, ioArgs); //allow the xhrGet's Deferred chain to continue.. return response; } }); }); </script> </head> <body> XHR request in progress. You have about 3 seconds to cancel it. <button onclick="javascript:request.cancel( )"
>Cancel</button> </body> </html>
If you run the example, you'll see the following output:
xhrGet just fired. Waiting on callbacks or errbacks now... Load response is: Hello Executing the callback chain now... Callback 1 says that the result is Hello Callback 2 says that the result is Hello
Whereas pressing the Cancel button yields the following results:
xhrGet just fired. Waiting on callbacks or errbacks now... Press the button to cancel... Error: xhr cancelled dojoType=cancel message=xhr cancelleddojo.xd.js (line 20) Error! Error: xhr cancelled dojoType=cancel message=xhr cancelled Executing the errback chain now... Errback 1 says that the result is Error: xhr cancelled dojoType=cancel message=xhr cancelled Errback 2 says that the result is Error: xhr cancelled dojoType=cancel message=xhr cancelled
The various XHR functions all have a special cancellation
function that is invoked by calling cancel( )
, but for custom Deferred
s, you can create your own
custom canceller, like so:
var canceller = function( ) { console.log("custom canceller..."); //If you don't return a custom Error, a default "Deferred Cancelled" Error is //returned } var d = new dojo.Deferred(canceller); //pass in the canceller to the constructor /* ....interesting stuff happens...*/ d.cancel( ); // errbacks could be ready to respond to the "Deferred Cancelled" Error //in a special way
While Deferred
is an
innate part of Base, Core provides DeferredList
, an additional supplement
that facilitates some use cases in which you need to manage
multiple Deferred
s. Common use
cases for DeferredList
include:
Firing a specific callback or callback chain when all of callbacks for a collection of
Deferred
s have firedFiring a specific callback or callback chain when at least one of the callbacks for a collection of
Deferred
s have firedFiring a specific errback or errback chain when at least one of the errbacks for a collection of
Deferred
s have fired
The API for DeferredList
follows:
dojo.DeferredList(/*Array*/list, /*Boolean?*/fireOnOneCallback, /*Boolean?*/ fireOnOneErrback, /*Boolean?*/consumeErrors, /*Function?*/canceller)
The signature should be self-descriptive in that calling the
constructor with only a single parameter that is an Array
of Deferred
s produces the default behavior
of firing the callback chain when the callback chains for all of
the Deferred
s have fired;
passing in Boolean parameters can control if the callback or
errback chain should be fired when at least one callback or
errback has fired, respectively.
Setting consumeErrors
to
true
results in errors being
consumed by the DeferredList
,
which is handy if you don't want the errors produced by the
individual Deferred
s in the
list to be directly exposed, and canceller
provides a way of passing in
custom cancellation function, just like with an ordinary Deferred
.
Get Dojo: The Definitive Guide now with the O’Reilly learning platform.
O’Reilly members experience books, live events, courses curated by job role, and more from O’Reilly and nearly 200 top publishers.