diff --git a/src/deferred.js b/src/deferred.js index 6e4d43b315..73b2f9ef2a 100644 --- a/src/deferred.js +++ b/src/deferred.js @@ -366,16 +366,21 @@ jQuery.extend( { // Single- and empty arguments are adopted like Promise.resolve if ( remaining <= 1 ) { - adoptValue( singleValue, master.resolve, master.reject ); + adoptValue( singleValue, master.done( updateFunc( i ) ).resolve, master.reject ); // Use .then() to unwrap secondary thenables (cf. gh-3000) - return master.then(); + if ( master.state() === "pending" || + jQuery.isFunction( resolveValues[ i ] && resolveValues[ i ].then ) ) { + + return master.then(); + } } // Multiple arguments are aggregated like Promise.all array elements while ( i-- ) { adoptValue( resolveValues[ i ], updateFunc( i ), master.reject ); } + return master.promise(); } } ); diff --git a/test/unit/deferred.js b/test/unit/deferred.js index 830103eeb5..9065553197 100644 --- a/test/unit/deferred.js +++ b/test/unit/deferred.js @@ -824,24 +824,27 @@ QUnit.test( "jQuery.when(nonThenable) - like Promise.resolve", function( assert QUnit.test( "jQuery.when(thenable) - like Promise.resolve", function( assert ) { "use strict"; - assert.expect( 56 ); - - var slice = [].slice, + var CASES = 16, + slice = [].slice, sentinel = { context: "explicit" }, eventuallyFulfilled = jQuery.Deferred().notify( true ), eventuallyRejected = jQuery.Deferred().notify( true ), + secondaryFulfilled = jQuery.Deferred().resolve( eventuallyFulfilled ), + secondaryRejected = jQuery.Deferred().resolve( eventuallyRejected ), inputs = { promise: Promise.resolve( true ), rejectedPromise: Promise.reject( false ), deferred: jQuery.Deferred().resolve( true ), eventuallyFulfilled: eventuallyFulfilled, - secondaryFulfilled: jQuery.Deferred().resolve( eventuallyFulfilled ), + secondaryFulfilled: secondaryFulfilled, + eventuallySecondaryFulfilled: jQuery.Deferred().notify( true ), multiDeferred: jQuery.Deferred().resolve( "foo", "bar" ), deferredWith: jQuery.Deferred().resolveWith( sentinel, [ true ] ), multiDeferredWith: jQuery.Deferred().resolveWith( sentinel, [ "foo", "bar" ] ), rejectedDeferred: jQuery.Deferred().reject( false ), eventuallyRejected: eventuallyRejected, - secondaryRejected: jQuery.Deferred().resolve( eventuallyRejected ), + secondaryRejected: secondaryRejected, + eventuallySecondaryRejected: jQuery.Deferred().notify( true ), multiRejectedDeferred: jQuery.Deferred().reject( "baz", "quux" ), rejectedDeferredWith: jQuery.Deferred().rejectWith( sentinel, [ false ] ), multiRejectedDeferredWith: jQuery.Deferred().rejectWith( sentinel, [ "baz", "quux" ] ) @@ -857,6 +860,7 @@ QUnit.test( "jQuery.when(thenable) - like Promise.resolve", function( assert ) { deferred: [ true ], eventuallyFulfilled: [ true ], secondaryFulfilled: [ true ], + eventuallySecondaryFulfilled: [ true ], multiDeferred: [ "foo", "bar" ], deferredWith: [ true ], multiDeferredWith: [ "foo", "bar" ] @@ -866,6 +870,7 @@ QUnit.test( "jQuery.when(thenable) - like Promise.resolve", function( assert ) { rejectedDeferred: [ false ], eventuallyRejected: [ false ], secondaryRejected: [ false ], + eventuallySecondaryRejected: [ false ], multiRejectedDeferred: [ "baz", "quux" ], rejectedDeferredWith: [ false ], multiRejectedDeferredWith: [ "baz", "quux" ] @@ -875,7 +880,9 @@ QUnit.test( "jQuery.when(thenable) - like Promise.resolve", function( assert ) { // Strict mode functions invoked without .call/.apply get global-object context defaultContext = (function getDefaultContext() { return this; }).call(), - done = assert.async( 28 ); + done = assert.async( CASES * 2 ); + + assert.expect( CASES * 4 ); jQuery.each( inputs, function( message, value ) { var code = "jQuery.when( " + message + " )", @@ -917,6 +924,8 @@ QUnit.test( "jQuery.when(thenable) - like Promise.resolve", function( assert ) { setTimeout( function() { eventuallyFulfilled.resolve( true ); eventuallyRejected.reject( false ); + inputs.eventuallySecondaryFulfilled.resolve( secondaryFulfilled ); + inputs.eventuallySecondaryRejected.resolve( secondaryRejected ); }, 50 ); } ); @@ -1049,3 +1058,28 @@ QUnit.test( "jQuery.when - notify does not affect resolved", function( assert ) assert.ok( false, "Error on resolve" ); } ); } ); + +QUnit.test( "jQuery.when(...) - opportunistically synchronous", function( assert ) { + + assert.expect( 5 ); + + var when = "before", + resolved = jQuery.Deferred().resolve( true ), + rejected = jQuery.Deferred().reject( false ), + validate = function( label ) { + return function() { + assert.equal( when, "before", label ); + }; + }, + done = assert.async( 5 ); + + jQuery.when().done( validate( "jQuery.when()" ) ).always( done ); + jQuery.when( when ).done( validate( "jQuery.when(nonThenable)" ) ).always( done ); + jQuery.when( resolved ).done( validate( "jQuery.when(alreadyFulfilled)" ) ).always( done ); + jQuery.when( rejected ).fail( validate( "jQuery.when(alreadyRejected)" ) ).always( done ); + jQuery.when( resolved, rejected ) + .always( validate( "jQuery.when(alreadyFulfilled, alreadyRejected)" ) ) + .always( done ); + + when = "after"; +} );