jQuery broken promises illustrated

jQuery

jQuery is a famous JavaScript library that just about every web programmer has heard of, and almost all have used it at least once in their web programming career.

jQuery has support for promises. It has nice documentation on them: see .promise(), deferred.promise(), and Deferred Object.

Promises

A promise is a way to reduce this

1
2
3
4
5
6
7
8
9
10
asyncCall(function(err, data1){
    if(err) return callback(err);
    anotherAsyncCall(function(err2, data2){
        if(err2) return calllback(err2);
        oneMoreAsyncCall(function(err3, data3){
            if(err3) return callback(err3);
            // are we done yet?
        });
    });
});

to this

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
asyncCall()
.then(function(data1){
    // do something...
    return anotherAsyncCall();
})
.then(function(data2){
    // do something...
    return oneMoreAsyncCall();
})
.then(function(data3){
   // the third and final async response
})
.fail(function(err) {
   // handle any error resulting from any of the above calls
})
.done();

The above two JavaScript excerpts have been taken from Promises – an alternative way to approach asynchronous JavaScript.

Several promise specification for JavaScript exist, Promises/A+ being the most widely used.

  1. Promises/A http://wiki.commonjs.org/wiki/Promises/A
  2. Promises/A+ https://promisesaplus.com/
  3. ES6 Promise Objects http://www.ecma-international.org/ecma-262/6.0/#sec-promise-objects

The problem with jQuery’s promises

jQuery’s promise implementation is pretty broken. It does not adheer to the Promises/A+ specification used by the vast majority of other promise implementations. See the list of Promises/A+ implementations.

An illustration

Using the latest of either jQuery 1.x or 2.x, let’s write the following JavaScript test program:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
var $, output, def, promise, myObject, syncWorkFunc, asyncWorkFunc;

setup();
promiseTests();

function setup() {
    $ = jQuery;
    output = document.getElementById('output');

    def = $.Deferred();
    promise = def.promise();

    myObject = {
        state: null
    };

    syncWorkFunc = function () {
        def.resolve();
    };
    asyncWorkFunc = function () {
        window.setTimeout(function () {
            def.resolve();
        }, 2000);
    };
}

function promiseTests() {
    $(output).append('0.) myObject.state = ' + myObject.state + '<br />');

    syncWorkFunc();
    // asyncWorkFunc();

    promise.then(function () {
        $(output).append('[THEN #1] Setting myObject.state to "X".<br />');
        myObject.state = "X";
    });
    $(output).append('1.) Setting myObject.state to "A".<br />');
    myObject.state = "A";

    // syncWorkFunc();
    // asyncWorkFunc();

    $(output).append('2.) myObject.state = ' + myObject.state + '<br />');
    promise.then(function () {
        $(output).append('[THEN #2] myObject.state = ' + myObject.state + '<br />');
    });
    promise.then(function () {
        $(output).append('[THEN #3] myObject.state = ' + myObject.state + '<br />');
    });
    promise.then(function () {
        $(output).append('[THEN #4] myObject.state = ' + myObject.state + '<br />');
    });
    $(output).append('3.)<br />');
}

The output of the above JavaScript program will be:

1
2
3
4
5
6
7
8
0.) myObject.state = null
[THEN #1] Setting myObject.state to "X".
1.) Setting myObject.state to "A".
2.) myObject.state = A
[THEN #2] myObject.state = A
[THEN #3] myObject.state = A
[THEN #4] myObject.state = A
3.)

Two things went wrong here. All of THEN {#} logs should be at the end of the output. Also, and this is most important of all, in the end myObject.state did not change to "X"!

If we use a real Promise/A+ implementation, we will get:

1
2
3
4
5
6
7
8
0.) myObject.state = null
1.) Setting myObject.state to "A".
2.) myObject.state = A
3.)
[THEN #1] Setting myObject.state to "X".
[THEN #2] myObject.state = X
[THEN #3] myObject.state = X
[THEN #4] myObject.state = X

You can see these results for yourself. A JS Fiddle has been setup with jQuery 2.1.4 here. For valid Promise/A+ implementation I chose Angular JS. It’s promise system $q is based on Kris Kowal’s Q. A JS Fiddle has been setup with Angular JS 1.4.2 here.

When will promises be fixed in jQuery?

When jQuery 3.0 comes out. Refer to the blog post jQuery 3.0: The Next Generations, and also jQuery’s work on getting their promises to pass the Promises/A+ test suite.

The problem discussed in this post is a well known one. It has been discussed previously by other people on the Internet. Be sure to go over all of the following resources:

  1. You’re Missing the Point of Promises
  2. working with jQuery promises
  3. JAVASCRIPT PROMISES AND WHY JQUERY IMPLEMENTATION IS BROKEN
  4. The Differences between jQuery Deferreds and the Promises/A+ spec
  5. Problems inherent to jQuery $.Deferred
  6. Coming from jQuery
  7. Deferred: Backwards-compatible standards interoperability