A World Of Promises, episode 2
This second article on Q will introduce you how to easily turn a callback API into a promise API using Deferred objects. It will also present the new W3C specification of Promise and finish with the implementation of Qimage, a simple Image Promise wrapper.
Deferred objects
Q splits the concept of Promise in two parts: one part is the Deferred object, another is the Promise object.
A Deferred object is an object which just aims to control the state of a Promise. It allows to do one of the two following actions (one time only):
.resolve(value)
: moving from pending to fulfilled with a value..reject(error)
: moving from pending to rejected with an error.
A Promise object can be obtained from a Deferred object via the promise
field.
In Q, a Promise is read-only: you can basically only do .then
with it
and there is no such resolve
or reject
method on a Promise.
a Deferred is "resolvable", a Promise is "thenable".
N.B.: this separation also exists in other languages but with different names (for instance in Scala: Promise / Future).
Q.defer()
The method Q.defer() will return a new Deferred object initialized in a pending state.
var d = Q.defer();
setTimeout(function () {
d.resolve(42);
}, 500);
var promise = d.promise;
// ...
promise.then(function (value) {
console.log("the universe = "+value);
});
Note that the Promises/A spec only specifies the concept of Promise. It does not defines the "Deferred" part. Up to the Promise library to implement its own way of resolving / rejecting the value of a Promise.
There is also Promises/B and Promises/D to define that though.
About the DOM.Promise specification
a new DOM specification draft has born recently and is a bit different from the Q style, the "Deferred" object (called a Resolver) is given as an argument of the function given at Promise instanciation.
var promise = new /*DOM.*/Promise(function (resolver) {
setTimeout(function () {
resolver.resolve(42);
}, 500);
});
promise.then(function (value) {
console.log("the universe = "+value);
});
Qimage: Wrapping the Image API
We will now show you how to easily wrap the DOM Image API into a Promise API with Q. Before showing the implementation, let's explore the possibilities of such API.
Qimage (url: String) => Promise[DOM Image]
Here is how we want our Qimage
API to look like:
var promise = Qimage("http://imagesource.com/image.png");
promise.then(function (image) {
// image instanceof Image
}, function (error) {
// error instanceof Error
});
We can use it like this:
Qimage("images/foo.png").then(function (img) {
document.body.appendChild(img);
}, function (error) {
document.body.innerHTML = "Unable to load the image";
});
Now we define Qimage
as a Promise library, we can then use all the power of Promises,
combine Promises together, chain different Promise APIs...
Q.all([
Qimage("res1.png"),
Qimage("res2.png"),
Qimage("res3.png")
])
.spread(function (res1, res2, res3) {
document.body.appendChild(res1);
document.body.appendChild(res2);
document.body.appendChild(res3);
});
This wrapper makes a simple but powerful Image Loading library module.
Implementation
Here is how Qimage
works:
var Qimage = function (url) {
var d = Q.defer();
var img = new Image();
img.onload = function () {
d.resolve(img);
};
img.onabort = function (e) {
d.reject(e);
};
img.onerror = function (err) {
d.reject(err);
};
img.src = url;
return d.promise;
};
...and that's it!
Note that the Deferred object is isolated in the Qimage
function scope.
Only the (read-only) Promise is accessible from the outside when returned by the function.
How simple is wrapping a callback API into a Promise API!
Qimage is released as a micro-lib and available on Github and NPM.
Next episode
Next episode will feature requestAnimationFrame
,
a fundamental function generally used for performing efficient Javascript animations.
We will show you QanimationFrame and how we can use it as a Promisified "wait for DOM to be ready" API.