Qep2.: Deferred objects, Qimage

AWOPjavascriptpromiseQlibrary

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.

generative artist who uses code to make art, explores the frontier of abstract art with algorithms pushing forward to more realistic scenery. Explore physical art via 'Plotting', which consist of drawing with fountain pens on robot. I don't do prints, I do plots: Every physical outcome is truly unique!