@GreWeb

Qep2.: Deferred objects, Qimage

AWOP javascript promise Q library

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.