Deferred: Make jQuery.when synchronous when possible

Closes gh-3102
Fixes gh-3100
Closes gh-3105
This commit is contained in:
Richard Gibson 2016-05-04 15:30:24 -04:00
parent e8825a529b
commit de71e9755f
2 changed files with 47 additions and 8 deletions

View File

@ -366,16 +366,21 @@ jQuery.extend( {
// Single- and empty arguments are adopted like Promise.resolve // Single- and empty arguments are adopted like Promise.resolve
if ( remaining <= 1 ) { 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) // 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 // Multiple arguments are aggregated like Promise.all array elements
while ( i-- ) { while ( i-- ) {
adoptValue( resolveValues[ i ], updateFunc( i ), master.reject ); adoptValue( resolveValues[ i ], updateFunc( i ), master.reject );
} }
return master.promise(); return master.promise();
} }
} ); } );

View File

@ -824,24 +824,27 @@ QUnit.test( "jQuery.when(nonThenable) - like Promise.resolve", function( assert
QUnit.test( "jQuery.when(thenable) - like Promise.resolve", function( assert ) { QUnit.test( "jQuery.when(thenable) - like Promise.resolve", function( assert ) {
"use strict"; "use strict";
assert.expect( 56 ); var CASES = 16,
slice = [].slice,
var slice = [].slice,
sentinel = { context: "explicit" }, sentinel = { context: "explicit" },
eventuallyFulfilled = jQuery.Deferred().notify( true ), eventuallyFulfilled = jQuery.Deferred().notify( true ),
eventuallyRejected = jQuery.Deferred().notify( true ), eventuallyRejected = jQuery.Deferred().notify( true ),
secondaryFulfilled = jQuery.Deferred().resolve( eventuallyFulfilled ),
secondaryRejected = jQuery.Deferred().resolve( eventuallyRejected ),
inputs = { inputs = {
promise: Promise.resolve( true ), promise: Promise.resolve( true ),
rejectedPromise: Promise.reject( false ), rejectedPromise: Promise.reject( false ),
deferred: jQuery.Deferred().resolve( true ), deferred: jQuery.Deferred().resolve( true ),
eventuallyFulfilled: eventuallyFulfilled, eventuallyFulfilled: eventuallyFulfilled,
secondaryFulfilled: jQuery.Deferred().resolve( eventuallyFulfilled ), secondaryFulfilled: secondaryFulfilled,
eventuallySecondaryFulfilled: jQuery.Deferred().notify( true ),
multiDeferred: jQuery.Deferred().resolve( "foo", "bar" ), multiDeferred: jQuery.Deferred().resolve( "foo", "bar" ),
deferredWith: jQuery.Deferred().resolveWith( sentinel, [ true ] ), deferredWith: jQuery.Deferred().resolveWith( sentinel, [ true ] ),
multiDeferredWith: jQuery.Deferred().resolveWith( sentinel, [ "foo", "bar" ] ), multiDeferredWith: jQuery.Deferred().resolveWith( sentinel, [ "foo", "bar" ] ),
rejectedDeferred: jQuery.Deferred().reject( false ), rejectedDeferred: jQuery.Deferred().reject( false ),
eventuallyRejected: eventuallyRejected, eventuallyRejected: eventuallyRejected,
secondaryRejected: jQuery.Deferred().resolve( eventuallyRejected ), secondaryRejected: secondaryRejected,
eventuallySecondaryRejected: jQuery.Deferred().notify( true ),
multiRejectedDeferred: jQuery.Deferred().reject( "baz", "quux" ), multiRejectedDeferred: jQuery.Deferred().reject( "baz", "quux" ),
rejectedDeferredWith: jQuery.Deferred().rejectWith( sentinel, [ false ] ), rejectedDeferredWith: jQuery.Deferred().rejectWith( sentinel, [ false ] ),
multiRejectedDeferredWith: jQuery.Deferred().rejectWith( sentinel, [ "baz", "quux" ] ) multiRejectedDeferredWith: jQuery.Deferred().rejectWith( sentinel, [ "baz", "quux" ] )
@ -857,6 +860,7 @@ QUnit.test( "jQuery.when(thenable) - like Promise.resolve", function( assert ) {
deferred: [ true ], deferred: [ true ],
eventuallyFulfilled: [ true ], eventuallyFulfilled: [ true ],
secondaryFulfilled: [ true ], secondaryFulfilled: [ true ],
eventuallySecondaryFulfilled: [ true ],
multiDeferred: [ "foo", "bar" ], multiDeferred: [ "foo", "bar" ],
deferredWith: [ true ], deferredWith: [ true ],
multiDeferredWith: [ "foo", "bar" ] multiDeferredWith: [ "foo", "bar" ]
@ -866,6 +870,7 @@ QUnit.test( "jQuery.when(thenable) - like Promise.resolve", function( assert ) {
rejectedDeferred: [ false ], rejectedDeferred: [ false ],
eventuallyRejected: [ false ], eventuallyRejected: [ false ],
secondaryRejected: [ false ], secondaryRejected: [ false ],
eventuallySecondaryRejected: [ false ],
multiRejectedDeferred: [ "baz", "quux" ], multiRejectedDeferred: [ "baz", "quux" ],
rejectedDeferredWith: [ false ], rejectedDeferredWith: [ false ],
multiRejectedDeferredWith: [ "baz", "quux" ] 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 // Strict mode functions invoked without .call/.apply get global-object context
defaultContext = (function getDefaultContext() { return this; }).call(), 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 ) { jQuery.each( inputs, function( message, value ) {
var code = "jQuery.when( " + message + " )", var code = "jQuery.when( " + message + " )",
@ -917,6 +924,8 @@ QUnit.test( "jQuery.when(thenable) - like Promise.resolve", function( assert ) {
setTimeout( function() { setTimeout( function() {
eventuallyFulfilled.resolve( true ); eventuallyFulfilled.resolve( true );
eventuallyRejected.reject( false ); eventuallyRejected.reject( false );
inputs.eventuallySecondaryFulfilled.resolve( secondaryFulfilled );
inputs.eventuallySecondaryRejected.resolve( secondaryRejected );
}, 50 ); }, 50 );
} ); } );
@ -1049,3 +1058,28 @@ QUnit.test( "jQuery.when - notify does not affect resolved", function( assert )
assert.ok( false, "Error on resolve" ); 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";
} );