It’s been a while since we’ve dove into Dojo’s Deferred module on the SitePen blog—the last time was back in 2010 when we introduced the promise-inspired improvements that landed with Dojo 1.5. Quite a lot has happened since on the topic, including a ground-up rewrite of Dojo’s asynchronous foundations in 1.8+, so it’s about time we take another look at deferreds and promises and how it all fits together in Dojo.
What’s a Promise?
Let’s start with some definitions. A “promise” is a concept to represent the result of an asynchronous operation. If this operation has yet to be completed, the promise is said to be “pending”, and once completed it is said to be “fulfilled”. If the operation was completed successfully the promise is said to be “resolved”. If not, the promise is said to be “rejected”. A promise that is resolved always has a value associated, even if that value is undefined
. A rejected promise has a corresponding exception—the reason for its rejection.
Promises, as defined above, have been in Dojo since 1.5, when the Dojo’s Deferred
module was extended to support this promise API. Since then promises have become ubiquitous—unless you’re new to the JavaScript language, or you’ve been under a rock for the last several years, it’s likely you’ve seen subject of promises kicked around. They’re becoming part of the language itself, and are beginning to show up in new web platform APIs.
What’s a Deferred?
A “deferred” is a special construct responsible for creating a new “promise”, and determining when this promise should be “resolved” or “rejected”.
Long time Dojo users are sure to be familiar with Dojo’s Deferred
class—after all, it’s been around since 0.3 was released in 2008. It’s implementation has evolved over the years, but had become a bit weighed down by the baggage of legacy. With Dojo 1.8 came a clean break with this baggage—a new Deferred
module was introduced with a fresh, minimal API. The previous Deferred
module is still available at dojo/_base/Deferred
, but it’s been deprecated in favor of the dojo/Deferred module and the suite of promise related modules in dojo/promise.
Deferred vs. Promise
Prior to Dojo 1.8 there really wasn’t—a promise was just an abstract concept, an API implemented by Deferred
instances. As of Dojo 1.8 promises are no longer abstract. They are real, distinct from deferred, and you’ll find them throughout asynchronous actions with the Dojo codebase. Every time a Deferred
is constructed, a corresponding Promise
is constructed too. The deferred carries with it the capability to “fulfill” its promise—that is, to “resolve” or “reject” it. This is quite a lot of power, and not the kind of capability you want to give to every caller of your asynchronous function.
On the other hand, the promise
instance associated with a Deferred
is purely a representation of some future value, and has no power to control what that value should be or when it becomes available. Its capabilities are intentionally restricted to allowing you to inspect and react to its fulfillment, potentially generating new promises representing a different eventual value. As it turns out, this is all the power necessary to consume an asynchronous API.
When to use Deferred?
A deferred contains both a promise and the ability to fulfill that promise. One way to think of it is as a deferred representing work that may or may not be finished, and its promise representing the value (or error) resulting from that work.
There aren’t many circumstances where you’ll need to create and fulfill a Deferred
instance—this is only necessary when you have to invoke an asynchronous action that doesn’t already return a promise, e.g. when converting a callback-based API into a promise-based API. Suppose we have a simple echoCallback
function, echoing back some provided string with an exclamation point after a brief, random delay using setTimeout
:
function echoCallback(value, callback) {
setTimeout(function() {
if (typeof value === "string") {
callback(new Error("Cannot echo " + typeof value));
} else {
callback(null, value + "!");
}
}, Math.random() * 1000);
}
This is a typical callback scenario using the error-first callback pattern popularized by Node.js. To turn this into a promise-based API, we create a function that invokes this echoCallback
function and returns a promise that represents its eventual completion. This function should create a new Deferred
instance, invoke the callback to set its fulfilled state, then return the deferred’s promise back to the caller:
require(["dojo/Deferred"], function(Deferred) {
function echo(value) {
var deferred = new Deferred();
// Assume `echoCallback` is already defined
echoCallback(value, function(error, result) {
if (error) {
deferred.reject(error);
}
else {
deferred.resolve(result);
}
});
return deferred.promise;
}
});
This example, if a bit contrived, is fairly complete—this is just about all there is to turning a callback-style function into one which returns a promise. Dojo’s asynchronous APIs already return promises so you shouldn’t have to write code like this often, but it helps highlight the two different roles played by a deferred and a promise.
It’s important to note that instances of Deferred
also implement the promise API, which means they can be used in place of their promise
value anywhere a promise is expected. So the code above could use return deferred
in place of return deferred.promise
, but this is frowned upon as it violates the Principle of Least Authority.
When to promise?
Promises in Dojo are not something you create yourself—they are a natural consequence of calling asynchronous functions in Dojo. Should you ever feel compelled to construct a Dojo Promise
what you really want is a Deferred
instead. But once you have a reference to a it can be used to generate subsequent promises, either with its then
method to tack on subsequent actions, or with the promise utilities introduced in Dojo 1.8.
dojo/when
When you don’t know for sure whether something is a promise or a value you can use the dojo/whenwhen utility is not new—it was introduced back in 1.5 as dojo.when
—but it’s worth calling out how important it is for consuming APIs which may or may not return a promise.
dojo/promise/all
The dojo/promise/all is useful when you have multiple promises and you want to wait for all of them to be fulfilled before taking some action. It takes any number promises, as an array or object, and returns a new promise that is fulfilled when all of these promises are resolved successfully, or as soon as one is rejected.
Given an array of promises the all
function will resolve to an array of values corresponding to the provided promises:
// Assume our `echo` function above is available as the "app/echo" module
require(["dojo/promise/all", "app/echo"], function(all, echo) {
all([echo("hello"), echo("there")]).then(function(results) {
// results: ["hello!", "there!"]
});
});
You can also provide an object mapping keys to promise values–the all
utility will return a new promise that resolves to an object with the keys preserved, and values are the resolved values of each respective promise:
require(["dojo/promise/all", "app/echo"], function(all, echo) {
all({ alice: echo("hello"), bob: echo("goodbye") })
.then(function(results) {
// results: { alice: "hello!", bob: "goodbye!" }
});
});
The array or object you pass to the all
utility can also include non-promise values, so you could think of it like a `whenAll` operation:
require(["dojo/promise/all", "app/echo"], function(all, echo) {
all({ alice: echo("promise value"), bob: "plain value" })
.then(function(results) {
// results: { alice: "promised value!", bob: "plain value!" }
});
});
dojo/promise/first
If you have multiple promises and you need to know as soon as the first one resolves then dojo/promise/first is the right tool do for the job. It takes multiple promises as an array or object (like dojo/promise/all
), and returns a promise that is fulfilled as soon as the first of these promises is fulfilled.
Say we have an array of promises, each representing a type of greeting, and we just need the value of the first one to resolve, without having to wait on any others:
require([
"dojo/promise/first", "dojo/_base/array", "app/echo"
], function(all, first, echo) {
// map `echo` to array of greeting strings to get promises for each
var greetings = array.map([
"Hello",
"Hi",
"Greetings",
"Hey up",
"Whatgwan"
], echo);
// as soon as the first greeting promise is fulfilled we can use it
first(greetings).then(function(greeting) {
// first available `greeting`, e.g. "Hello!" or "Whatgwan!"
// selected randomly due to delay added by `echo` function
});
});
Conclusion
The real power of promises—in Dojo and JavaScript in general—comes from the abstractions they introduce that allows us to model problems closer to the way we think of them. We can compose them together—do this, then that, then all this other stuff—in ways that can make the intent of these actions more explicit. Promises are a simple but powerful tool in the long-running battle to tame the complexity arising from wrangling asynchronicity in everyday code.
We’ve just scratched the surface but we’ve shown a glimpse of how these abstractions can be used, and even what it takes to build them up from scratch. Hopefully this helps to demonstrate some of the patterns and practices made possible by the changes in Dojo 1.8+, and gives you an idea of how this can be applied to your asynchronous problems to make them more legible and maintainable.