Qep1.: Q, a Promise library

AWOPjavascriptpromiseQlibrary

A World Of Promises, episode 1

This article is the first of a series of small articles on the Q Javascript library and its eco-system. This article is a brief introduction to Q Promises.

Q is a Promise library in Javascript created 4 years ago by Kris Kowal who is one of the main contributor to CommonJS where we can find the Promises/A specification.

Q is probably the most mature and powerful Promise library in Javascript which inspired a lot of libraries like jQuery. It exposes a complete API with, in my humble opinion, good ideas like the separation of concerns between a "Deferred" object (the resolver) and a "Thenable" Promise (the read-only promise).

This article is a brief introduction to Q Promises with some examples. For more information on the subject, I highly recommend reading the article "You're Missing the Point of Promises" and the Q implementation design README.

What is a Promise

A Promise is an object representing a possible future value which has a then method to access this value via callback. A Promise is initially in a pending state and is then either fulfilled with a value or rejected with an error.

Schema representing Promise states: pending -> fulfilled|rejected

Some properties

It is immutable because the Promise value never changes and each then creates a new Promise. As a consequence, one same Promise can be shared between different code.

It is chainable through the then method (and other Q shortcut methods), which transforms a Promise into a new Promise without knowing what's inside.

It is composable because the then method will unify any Promise returned as a result of the callback with the current Promise (act like a map or flatmap). Q also has a Q.all helper for combining an Array of Promise into one big Promise.

A solution against the Pyramid of Doom effect

Javascript is by nature an asynchronous language based on an event loop which enqueue events. As a consequence, there is no way to block long actions (like Image Loading, ajax requests, other events), but everything is instead asynchronous: Most of Javascript APIs are using callbacks - functions called when the event has succeeded.

Problem with callbacks is when you start having a lot of asynchronous actions. It quickly becomes a Callback Hell.

Example

Here is a simple illustration, let's say we have 2 functions, one for retrieving some photos meta-infos from Flickr with a search criteria: getFlickrJson(search, callback), another for loading an image from one photo meta-info: loadImage(json, callback). Of-course both functions are asynchonous so they need a callback to be called with a result.

With this callback approach, we can then write:

// search photos for "Paris", load and display the first one
getFlickrJson("Paris", function (imagesMeta) {
  loadImage(imagesMeta[0], function (image) {
    displayImage(image);
  });
});

(Imagine what it can look like with more nested steps.)

we can easily turn a callback API into a Promise API

Promise style

getFlickrJson and loadImage can now be rewritten as Promise APIs:

Each function has clean signatures:

  • getFlickrJson is a (search: String) => Promise[Array of ImageMeta].
  • loadImage is a (imageMeta: ImageMeta) => Promise[DOM Image].
  • displayImage is a (image: DOM Image) => undefined.

...and are easily pluggable together:

getFlickrJson("Paris")
  .then(function (imagesMeta) { return imagesMeta[0]; })
  .then(loadImage)
  .then(displayImage, displayError);

This is much more flatten, concise, maintainable and beautiful!

Note that if we want to be safer we can write:

Q.fcall(getFlickrJson, "Paris")
  .then(function (imagesMeta) { return imagesMeta[0]; })
  .then(loadImage)
  .then(displayImage, displayError);

Q.fcall will call the function with the given parameters and ensure wrapping the returned value into a Promise. So my code should continue working even if we change signatures to:

  • getFlickrJson is a (search: String) => Array of ImageMeta.
  • loadImage is a (imageMeta: ImageMeta) => DOM Image.

One other cool thing about this chain of Promises is we can easily add more steps between two then step, for instance a DOM animation, a little delay, etc.

Error Handling

But a much important benefit is, unlike the callbacks approach, we can properly handle the error in one row because one of the following steps eventually fails:

  • getFlickrJson fails to perform the ajax request to retrieve the Flickr JSON data.
  • The array returned by Flickr was empty so loadImage throws an exception.
  • The loadImage fails (e.g. the image is unavailable).

This is called propagation and is exactly how exceptions work.

Promise Error Handling really looks like Exception Handling.

If it would be possible to have two methods:

  • getFlickrJsonSync is a (search: String) => Array of ImageMeta.
  • loadImageSync is a (imageMeta: ImageMeta) => DOM Image.

Then, the blocking code would look like this:

try {
  var imagesMeta = getFlickrJsonSync("Paris")
  var firstImageMeta = imagesMeta[0]
  var image = loadImageSync(firstImageMeta)
  displayImage(image);
} catch (e) {
  displayError(e);
}

...which is very close to Promise style.

Q Promises also unify Exceptions and Rejected Promises: throwing an exception in any Q callback will result in a rejected Promise.

var safePromise = Q.fcall(function () {
  // following eventually throws an exception
  return JSON.parse(someUnsafeJsonString);
});
// safePromise is either fulfilled with a JSON Object
// or rejected with an error.

Error handling with the callbacks approach is hell:

getFlickrJson("Paris", function (imagesMeta) {
  if (imagesMeta.length == 0) {
    displayError();
  }
  else {
    loadImage(imagesMeta[0], function (image) {
      displayImage(image);
    }, displayError);
  }
}, displayError);

Next episode

Next episode, we will show you how to create your own Promises with Deferred objects. We will introduce Qimage, a simple Image loader wrapped with Q.


Special Kudos to @42loops & @bobylito for bringing Q in my developer life :-P

As a generative plotter artist, I use code to generate art (creative coding) and physically create it with pen plotters, which is itself a generative process – each physical plot is a unique variant. I love dualities, like digital vs analog physical, abstract vs figurative, orthogonal vs polar, photo vs noise,...