Skip to content
Kris Kowal edited this page Nov 11, 2011 · 12 revisions

Welcome! Q works well alongside jQuery’s promise system, but the patterns are a little bit different.

In Q, then is the heart of the library, but it is not the same as then in jQuery. It more like jQuery’s pipe.

Chaining

jQuery’s then method is consistent with jQuery’s internal ideas about chaining, but inconsistent with common usage of promises. In jQuery, chaining functions usually return the this object of the call so you can build up lots of handlers on a single object. With promises, though, it is far more common that you will only need to set up handlers on the promise once. pipe is far more useful for modeling return, try, catch, and finally asynchronously. All of Q’s methods on promises return a new promise, not this. The new promise gets resolved with the return value of either the fulfillment handler or the error handler. Only one of these functions will be called, and each function can only return a value or throw an exception. If a callback throws an exception, the promise gets rejected. If a callback returns a promise, it gets “piped” or “forwarded”, allowing the returned promise to make progress without necessarily being “fully resolved” the same turn.

This is an example from jQuery on how to call a chain of asynchronous functions, feeding the result of each step into the next.

var request = $.ajax( url, { dataType: "json" } ),
    chained = request.pipe(function( data ) {
      return $.ajax( url2, { data: { user: data.userId } } );
    });
chained.done(function( data ) {
  // data retrieved from url2 as provided by the first request
});

Here’s an example of using Q and jQuery together for the same job. We’re using Q.when in this case to convert jQuery’s promise into a Q promise.

Q.when($.ajax(url, {dataType: "json"}))
.then(function (data) {
    return Q.when($.ajax(url2, {data: {user: data.userId}}));
})
.then(function (data) {
    // data retrieved from url2
})

Here’s what it can look like with Q and a library that returns Q promises. We’re using Q.call to make the indentation level of the operations even, to guarantee that each operation happens in its own event, and to guarantee that if an exception is thrown by getJson, even in the first turn, that exception will percolate down the chain.

Q.call(function () {
    return getJson(url);
})
.then(function (data) {
    return getJson(url2, {data: {user: data.userId}});
})
.then(function (data) {
    // data retrieved from url2
})

Multiple values

jQuery’s promises represent multiple values. Q comes from an older school of thought, where a promise represents either the return value or the exception thrown by a function. Q’s syntax is optimized for pipelines, whereas jQuery’s is optimized for multi-value promises and parallel handling of one promise.

This is a jQuery observing multiple promises and observing all of their results together.

$.when(x, y, z)
.then(function (x, y, z) {
})

With Q, you explicitly ask Q.all to provide a promise for an array of fullfilled values from an array of promises. You can use then to get the array.

Q.all([x, y, z])
.then(function (xyz) {
    var x = xyz[0];
    var y = xyz[1];
    var z = xyz[2];
});

But, if you have an array with a known length that you want to spread into the variadic arguments of your fulfillment handler, you can use the spread function.

Q.all([x, y, z])
.spread(function (x, y, z) {
})

all and spread work independently, so you can combine them in a lot of different ways.

Q.call(function () {
    return [getA(), getB(), getC()];
})
.all()
.spread(function (a, b, c) {
})
getX().then(function (x) {
    return Q.all([
        x.getA(),
        x.getB(),
        x.getC()
    ]);
);

Multiple observers

It’s also a common pattern in jQuery that any function that accepts a single function can also accept an array of functions that will be applied in order. That would not work with jQuery’s pipe and it does not work with Q’s then. The reason for this is that then returns a new promise that gets resolved by the return value or thrown exception of the callback or the errback. Only one of these functions can be called, so which function resolves the promise is unambiguous.

return promise.then(
function win(value) {
    return value; 
}, function fail(error) {
    throw error;
});

If then were to accept multiple functions for the callback or errback, they would fight for the right to resolve the promise, and it is extreemly rare that you will want to observe the fulfillment or rejection of the same promise more than once in the same place. However, if this is what you want, you can:

var promise = getPromise();
promise.then(function (value) {
})
promise.then(function (sameValue) {
})

Deferreds, Promises, Resolvers

jQuery balances the principle of least-authority differently than Q. Q separates authority by default and jQuery provides a mechanism for separating authority.

Both Q and jQuery provide a deferred object that hosts the authorities of observing progress and causing progress. In Q, the deferred has a promise object that is the sole interface for observing progress. The resolve and reject functions make progress. Only the promise has the promise API. In jQuery, the deferred object is both the promise and the deferred, but it provides a promise function that can either return the promise part of the API, or put the promise methods on another object.

function foo() {
    var result = Q.defer();
    result.resolve(10);
    return result.promise;
}
function foo() {
    var result = $.Deferred();
    result.resolve(10);
    return result; // or
    return result.promise();
}

Q is less prone to accidental gifting of excess authority. It’s easier to give than to take back.

Progress

Q does not presently support progress notification.

Context

Q does not track the context object that goes with a fulfilled value since this cannot be expressed with a return value of thrown exception in a callback. As such, if you want a particular this bound in your handler functions, you will need to bind it yourself or bind it to another value in scope.

var self = this;
promise.then(function () {
    // use self
});
promise.then(function () {
    // use this
}.bind(this));

Reference

  • always (promise, deferred) is fin for “finally” and is supported both as promise.fin(handler) and Q.fin(promise, handler).

  • done (promise, deferred): use promise.then(win) or Q.when(promise, win). then and when do not support multiple handlers, but any argument can be falsy, in which case the resolution will be forwarded implicitly.

  • fail (promise, deferred) is the same name but does not support multiple handlers.

  • isRejected (promise, deferred): use Q.isRejected(promise)

  • isResolved (promise, deferred): use Q.isResolved(promise). Q also has Q.isFulfilled(promise) to distinguish between fulfillment and rejection, which are both types of resolution.

  • notify (deferred): Q does not yet support progress notification.

  • notifyWith (deferred): Q does not yet support progress notification.

  • pipe (promise, deferred) is simply promise.then(win, fail) or Q.when(promise, win, fail).

  • progress (deferred): Q does not support progress handlers.

  • promise (deferred): use deferred.promise as a property, not a function. You must get the promise part of a deferred; the deferred does not have the promise API.

  • reject (deferred) is the same.

  • rejectWith (deferred): use bind or bind this to another value in scope.

  • resolve (deferred) is the same.

  • resolveWith (deferred): use bind or bind this to another value in scope.

  • state (promise, deferred): use isResolved, isFulfilled or isRejected. It is very rare to need to observe the internal state in the same event.

  • then (promise, deferred): use then, but it does not return the this object; it returns a new promise. If you want to use the same promise multiple times, you will need to capture it in a variable to reuse it, which will probably never be necessary.

  • when ($) is similar to Q.when(promise, win, fail) and can be used in the same fashion for single-value promises since win and fail handlers are optional and forward to the returned promise if omitted.