The Differences between jQuery Deferreds and the Promises/A+ spec


A lot of developers use jQuery Deferreds to achieve promise-like behaviour. Since deferreds work for most scenarios, most do not know that jQuery deferreds are not compliant with the Promise/A+ spec. Surprised? Well, there are probably promise implementations that fall short of the spec.

The schism is minor and might not really matter if promise libraries are not mixed. However, it is definitely good to know the difference – you never know when it’ll come in handy. So what’s the big issue and how does it affect developers?

The first difference is in the implementation of then; according to the Promise/A+ spec, then MUST return a promise regardless of what happens in its onresolved and onrejected handlers. Thus explicit reject calls, exceptions or syntax errors will all lead to a rejected promise. jQuery deferreds have a different view of the world altogether – unhandled errors will bubble up until they are caught or reach window.onerror.

Lets examine both scenarios, first the promise:

//dummy resolved promise
var p1 = Promise.resolve();

var p2 = p1.then(function() {
    throw new Error("Exception!");
});

console.log(p2);
//Promise {[[PromiseStatus]]: "rejected",
//[[PromiseValue]]: Error: Exception!}

And now, jQuery deferreds

var foo = new jQuery.Deferred();
var bar = foo.then(function (rslv) {
    throw Error('Exception!');
});

foo.resolve();
//Uncaught -> Error: Exception!

bar.state();
//pending

Another minor difference is the then function’s arity: the Promise/A+ specification says then should be dyadic while the Deferred’s then function is triadic. The jQuery implementation probably goes all the way back to the first promise proposal.

//Promise/A+
jsPromise.then(onresove,onreject);

//jQuery Deferreds
deferred.then(onresolve,
              onreject,
              onprogress);

Why should I care?

Assume you want to try a potentially disruptive operation when a promise resolves. If you’re using a Promise/A+ compliant library, all you have to do is check for rejected promises. The resolution state value will contain the information about success/failures. This is simple as there is no need to explicitly handle errors and consistent as you use the asynchronous programming promise style all through.

Deferreds will require you to explicitly handle all failures (e.g. by using try-catch blocks). This leads to a weird mixture of the asynchronous (promise-style) and synchronous (error-handling) programming styles. Moreover, if the error is unhandled, you can bid bye to all queued-up chained operations.

I am not going to say which approach is better – that’s your decision.

Yes! Workarounds

There are two workarounds : converting deferreds to Promises or ensuring the deferred then handler returns a promise.

The promise API will handle ‘thenables‘ (objects with a then function) as promises so mixing between different promise implementations is OK. Deferreds can be converted to promises (most libraries expose methods to do this).

To ensure deferreds always return promises, this involves wrapping error-throwing operations in try/catch handlers and rejecting promises when exceptions occur.

Let see a code example below:

var log = console.log.bind(console);
var foo = new jQuery.Deferred();
var bar = foo.then(function (rslv) {
 var tmp = new jQuery.Deferred();
 try {
    throw Error('Exception!');
 }
 catch (e) {
    tmp.reject();
 }
 return tmp.promise();
});
bar.fail(function (val) {
 log('Exception thrown and handled');
});
foo.resolve();

In case you are wondering, the jQuery promise derives from the jQuery deferred and has the same issues while fail is syntactic sugar for handling promise failures. The promises-tests repo can be used to evaluate implementations for Promise/At+ compliance :).

jQuery deferreds are not Promise/A+ compliant.

5 thoughts on “The Differences between jQuery Deferreds and the Promises/A+ spec

  1. We just ripped out jquery deferreds from our project. We’re now using bluebird. Why?

    Jquery deferred is NOT always asynchronous. This represents a huge design problem when you’re writing code that you think is always async.

    Errors. Exceptions get swallowed up easily with jquery deferred. Horrible, horrible.

    Finally, if that’s not bad enough, we switched to Bluebird because we got an enormous performance increase.

    Like

  2. Q is great. We settled on Bluebird because it’s the fastest. Both are great libraries. Our experience with Bluebird has been very positive and I highly recommend it.

    Like

Leave a comment

This site uses Akismet to reduce spam. Learn how your comment data is processed.