Deferreds

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 Deferreds, 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

Name

Return type

Comment

addCallback(/*Function*/handler)

Deferred

Adds a callback function to the callback chain for successes.

addErrback(/*Function*/handler)

Deferred

Adds a callback function to the callback chain for errors.

addBoth(/*Function|Object*/ context, /*String?*/name)

Deferred

Adds a callback function that acts as both the callback for successes and errors. Useful for adding code that you want to guarantee will run one way or another.

addCallbacks(/*Function*/callback, /*Function*/errback)

Deferred

Allows you to add a callback and an errback at the same time.

callback(/*Any*/value)

N/A

Executes the callback chain.

errback(/*Any*/value)

N/A

Executes the errback chain.

cancel( )

N/A

Cancel the request and execute the cancellation function provided to the constructor, if provided.

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

Deferred Examples Via CherryPy

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.

Using Deferreds returned from XHR functions

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.

The basic flow of events through a Deferred

Figure 4-2. The basic flow of events through a Deferred

Injecting Deferreds into XHR functions

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 Deferred
             request = 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

Custom canceller

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

DeferredList

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 fired

  • Firing a specific callback or callback chain when at least one of the callbacks for a collection of Deferred s have fired

  • Firing 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.