-
Notifications
You must be signed in to change notification settings - Fork 1.2k
jQuery
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
.
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
, and catch
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
})
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()
]);
);
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) {
})
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.
Q does not presently support progress notification.
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));
-
always
(promise, deferred) isfin
for “finally” and is supported both aspromise.fin(handler)
andQ.fin(promise, handler)
. -
done
(promise, deferred): usepromise.then(win)
orQ.when(promise, win)
.then
andwhen
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): useQ.isRejected(promise)
-
isResolved
(promise, deferred): useQ.isResolved(promise)
. Q also hasQ.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 simplypromise.then(win, fail)
orQ.when(promise, win, fail)
. -
progress
(deferred): Q does not support progress handlers. -
promise
(deferred): usedeferred.promise
as a property, not a function. You must get thepromise
part of a deferred; the deferred does not have the promise API. -
reject
(deferred) is the same. -
rejectWith
(deferred): usebind
or bindthis
to another value in scope. -
resolve
(deferred) is the same. -
resolveWith
(deferred): usebind
or bindthis
to another value in scope. -
state
(promise, deferred): useisResolved
,isFulfilled
orisRejected
. It is very rare to need to observe the internal state in the same event. -
then
(promise, deferred): usethen
, but it does not return thethis
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 toQ.when(promise, win, fail)
and can be used in the same fashion for single-value promises sincewin
andfail
handlers are optional and forward to the returned promise if omitted.