Star Flow
Star Flow

Reputation: 91

jQuery.Deferred.prototype.then: returns an object with behaviour I don't understand

I've been having trouble with jQuery.Deferred.prototype.then, so I decided to look at jQuery's test suite to check if I correctly understand the behaviour of this method. The test which is most relevant to my problem is the following, from release 3.2.1:

https://github.com/jquery/jquery/blob/3.2.1/test/unit/deferred.js#L133-L168

Note:

But the above 3 points can't all be correct!

Both calls to done are made on an object returned by jQuery.Deferred.prototype.then. I can explain the code if I posit that, in the Promise object returned by then, the final status is different from the status of the original Deferred. However, I can find no hint of this in the jQuery documentation.

To put my question as briefly as possible: when the code that I have linked to above is executed, are the callbacks that are passed to done executed, and if so, why?

UPDATE

Here is the code that I linked to above (with a few comments added to indicate line numbers):

QUnit.test( "jQuery.Deferred.then - filtering (fail)", function( assert ) {

        assert.expect( 4 );

        var value1, value2, value3,
                defer = jQuery.Deferred(),
                piped = defer.then( null, function( a, b ) {
                        return a * b;
                } ),
                done = jQuery.map( new Array( 3 ), function() { return assert.async(); } );

        piped.done( function( result ) {   // Line 144
                value3 = result;
        } );

        defer.fail( function( a, b ) {
                value1 = a;
                value2 = b;
        } );

        defer.reject( 2, 3 ).then( null, function() {   // Line 153
                assert.strictEqual( value1, 2, "first reject value ok" );
                assert.strictEqual( value2, 3, "second reject value ok" );
                assert.strictEqual( value3, 6, "result of filter ok" );
                done.pop().call();
        } );

        jQuery.Deferred().resolve().then( null, function() {
                assert.ok( false, "then should not be called on resolve" );
        } ).then( done.pop() );

        jQuery.Deferred().reject().then( null, jQuery.noop ).done(   // Line 164
function( value ) {
                assert.strictEqual( value, undefined, "then fail callback can return undefined/null" );
                done.pop().call();
        } );
} );

UPDATE 2

It turns out that the behaviour of then changed with the release of jQuery 3 in June 2016. According to a post on the jQuery blog, announcing the new release:

The resolution state of a Deferred created by .then() is now controlled by its callbacks—exceptions become rejection values and non-thenable returns become fulfillment values. Previously, returns from rejection handlers became rejection values.

The documentation for then hasn't been updated yet.

Upvotes: 1

Views: 181

Answers (1)

Bergi
Bergi

Reputation: 664327

The Deferred object is rejected on line 153.

This is a pretty horrible test, doing many things at once. There are many deferreds and many promises, some of them completely unrelated to each other.

Notice that on line 153, the defer deferred is rejected, which does have a .fail callback attached, .then callbacks attached (which created piped), and more .then callbacks attached in line 153 itself.

done is called on lines 144 and 164. I'm not that familiar with QUnit, but it's pretty clear to me that the test will fail unless the callbacks that are passed to done are actually executed.

No. You must not confuse the deferred .done method and QUnit's done callbacks. In fact, there are three QUnit callbacks stored in the done array, each of them created by assert.async().

The callbacks passed to done should be executed if and only if the Deferred object is resolved, not rejected.

Yes, this is exactly what happens. Notice that the .done calls in lines 144 and 164 are not made on the rejected defer deferred, but rather on the piped promise and another anonymous promise created by .then(null, jQuery.noop). Those promises are not rejected, they are fulfilled with the result of the onrejected callback that was passed as the second argument to the respective .then call.

Upvotes: 2

Related Questions