The dojo.Deferred module has long been a central component of Dojo, providing a powerful interface for asynchronous operations like HTTP requests. Dojo’s Deferreds are a form of promises, providing a separation of concerns between the mechanics of calling a function and the interface for interacting with the eventual asynchronous future result. Passing callbacks directly to functions tightly couple the interface with asynchronous semantics, but using Dojo’s Deferreds (promises) keeps the concerns separate for robust asynchronous code. Furthermore promises can greatly simplify interaction, allowing one to easily get the eventual result of an operation whether it has already finished, or if it is yet to be finished. While the Deferred module has existed in Dojo for some time, version 1.5 introduces some valuable enhancements.

One of the primary new methods on dojo.Deferred is the the new then() method, which can be used in place of the addCallback() or addBoth() methods with some important advantages that we will investigate. First a quick sample:

dojo.xhrGet({url:...}).then(function(response){
   someElement.innerHTML = response;
});

Before looking more closely at the new API, the primary new concept in Deferred 1.5 is immutable-once-resolved promises. The advantage of immutable-once-resolved promises can be explained with analogy to synchronous coding. A key characteristic of JavaScript is pass-by-value call semantics. If we execute this function:

function foo(){
  var a = 2;
  var b = bar(a);
  alert(a);
}

The call to bar passed the value of a, not a direct memory reference to the variable’s memory slot. Therefore, we can be assured that there is nothing that the bar function can do that will alter the value of a (now obviously if a referred to an object, that object could be mutated, but a‘s reference to the object could not be changed, just as its reference to an immutable primitive can’t be changed). The bar function only has control of what is returned to b, and we can safely assume that the alert will be passed a value of 2. This greatly simplifies programming because it is a more functional, safe approach, one does not need to worry as much about the side-effects of called functions.

Likewise, immutable-once-resolved promises act the same way. If one passes a promise to another function, one shouldn’t need to worry about the promise being altered. Two separate functions (such as the caller and callee function) should be able to utilize the same promise without fear of affecting each other inadvertently. We should be capable of using the values generated from functions in various ways. With normal sync functions, a value can be returned, and we can use that value in a variety of ways. We can set it to a variable, use it to make computations, pass it to other functions, etc. By encapsulating the eventual completion and result of an asynchronous function’s execution in a promise, we can do the same thing with promises. We can pass them to other functions, we can register computations to be done on the values, etc. The recipients of promises can safely use the eventual value (registering to receive it) without cause side-effects to each other. Unfortunately, the old dojo.Deferred objects were mutating, the return value of an addCallback changed the value of the deferred. Now, the API provides access to immutable promises that can be safely passed around.

then()

The rewrite of dojo.Deferred provides a few new access points while preserving backwards compatibility with the previous dojo.Deferred versions. The first new method, as mentioned before, is the then() function. This method is based on this CommonJS promise proposal, and discussed in my article on the CommonJS platform. Calling then() is similar to dojo.Deferred’s addCallback or addBoth function. The first argument is the callback, and the second argument is the error handler. Both are optional. The main difference between then() and addCallback or addBoth is that it is side-effect free. The value returned from then() is a new promise, which can be chained but not altered. Consequently, we can write:

var responsePromise = dojo.xhrGet({url:...});
showStatus(responsePromise);
responsePromise.then(function(response){
   return dojo.fromJson(response).content;
}).then(function(content){
   someElement.innerHTML = content;
});

One key thing to note is that even though we passed the result of the XHR operation to the function that parses the JSON and sets the content into some HTML, we did not modify responsePromise. It still resolves to the text returned from the XHR request, and can safely be passed to other functions (like showStatus() in this example) without interference.

promise property

Part of ensuring that promises are unaffected by other code is by only exposing a minimal API on the promises. Deferred objects (result of calling new dojo.Deferred()) offer callback and errback functions for resolving, but the objects returned by then() are purely consumer objects and therefore only provide a then() function. However, it is possible to directly use a consumer-only promise object from Deferred with the promise property without having to go through a then() call. For example:

var deferred = new dojo.Deferred();
foo(deferred.promise);
function foo(promise){
   promise.callback(); // this will fail, pure promises can't be directly resolved
   promise.then(function(){
     // this will succeed, pure promise's provide listening
   });
}
deferred.callback(3); // this will succeed, the deferred object provides resolution methods

dojo.when

Perhaps the most useful new function is the addition of dojo.when(). The dojo.when() function can also be used as a static function to register a callback for the eventual completion of a promise or deferred (with chaining support and without side-effect, like then()), but dojo.when() can be used consistently on both normal non-promise values and promises. It therefore provides a powerful form of synchronous/asynchronous normalization. A simple example of using dojo.when, based on our initial example:

dojo.when(dojo.xhrGet({url:...}), function(response){
   someElement.innerHTML = response;
});

This doesn’t look much better than any other way of calling dojo.xhrGet, but lets look at another example to see the advantage of using dojo.when(). Suppose we write a function that finds the last item in an array and prints it. A simple synchronous implementation might look like:

function printLast(items){
   console.log(items[items.length - 1]);
}

Now suppose we wanted this function to work on promises, we could of course alter the function to do so:

function printLastAsync(items){
   items.then(function(items){
     console.log(items[items.length - 1]);
   });
}

However, what if we want our function to work properly with both asynchronous promises as well standard synchronous values? With dojo.when() we can write something like:

function printLast(items){
   dojo.when(items, function(items){
     console.log(items[items.length - 1]);
   });
}

Now our findLast can be used uniformly with both normal synchronous value and promise/deferred based values. For example:

printLast([1,2,3])
> 3
printLast(dojo.xhrGet({handleAs:"json",url:...}));
> (last value returned from server)

And remember that dojo.when() returns a new promise like then(), so we can still chain promise actions together. For example:

dojo.when(dojo.when(dojo.xhrGet({handleAs:"json",url:...}), 
    function(data){
       return process(data);
    }),
  function(processedData){
    return doSomething(processedData);
  });

Dojo’s new Deferred module provides backwards compatibility with previous versions, still providing addCallback, addErrback, addBoth, and addCallbacks methods, but these will probably be deprecated in a future release (chaining will still be possible though then() and dojo.when()).

The new Deferred module provides a new set of easy-to-use methods facilitating robust, manageable, asynchronous code. The new module provides promises with minimal API, preserving the object capability model‘s principal of least authority, functional side-effect free data flow, and easy synchronous/asynchronous normalization.