throw ex;
}
+function adoptValue( value, resolve, reject ) {
+ var method;
+
+ try {
+
+ // Check for promise aspect first to privilege synchronous behavior
+ if ( value && jQuery.isFunction( ( method = value.promise ) ) ) {
+ method.call( value ).done( resolve ).fail( reject );
+
+ // Other thenables
+ } else if ( value && jQuery.isFunction( ( method = value.then ) ) ) {
+ method.call( value, resolve, reject );
+
+ // Other non-thenables
+ } else {
+
+ // Support: Android 4.0 only
+ // Strict mode functions invoked without .call/.apply get global-object context
+ resolve.call( undefined, value );
+ }
+
+ // For Promises/A+, convert exceptions into rejections
+ // Since jQuery.when doesn't unwrap thenables, we can skip the extra checks appearing in
+ // Deferred#then to conditionally suppress rejection.
+ } catch ( /*jshint -W002 */ value ) {
+
+ // Support: Android 4.0 only
+ // Strict mode functions invoked without .call/.apply get global-object context
+ reject.call( undefined, value );
+ }
+}
+
jQuery.extend( {
Deferred: function( func ) {
},
// Deferred helper
- when: function() {
- var method, resolveContexts,
- i = 0,
- resolveValues = slice.call( arguments ),
- length = resolveValues.length,
+ when: function( singleValue ) {
+ var
+
+ // count of uncompleted subordinates
+ remaining = arguments.length,
- // the count of uncompleted subordinates
- remaining = length,
+ // count of unprocessed arguments
+ i = remaining,
+
+ // subordinate fulfillment data
+ resolveContexts = Array( i ),
+ resolveValues = slice.call( arguments ),
- // the master Deferred.
+ // the master Deferred
master = jQuery.Deferred(),
- // Update function for both resolving subordinates
+ // subordinate callback factory
updateFunc = function( i ) {
return function( value ) {
resolveContexts[ i ] = this;
resolveValues[ i ] = arguments.length > 1 ? slice.call( arguments ) : value;
if ( !( --remaining ) ) {
- master.resolveWith(
- resolveContexts.length === 1 ? resolveContexts[ 0 ] : resolveContexts,
- resolveValues
- );
+ master.resolveWith( resolveContexts, resolveValues );
}
};
};
- // Add listeners to promise-like subordinates; treat others as resolved
- if ( length > 0 ) {
- resolveContexts = new Array( length );
- for ( ; i < length; i++ ) {
-
- // jQuery.Deferred - treated specially to get resolve-sync behavior
- if ( resolveValues[ i ] &&
- jQuery.isFunction( ( method = resolveValues[ i ].promise ) ) ) {
-
- method.call( resolveValues[ i ] )
- .done( updateFunc( i ) )
- .fail( master.reject );
-
- // Other thenables
- } else if ( resolveValues[ i ] &&
- jQuery.isFunction( ( method = resolveValues[ i ].then ) ) ) {
-
- method.call(
- resolveValues[ i ],
- updateFunc( i ),
- master.reject
- );
- } else {
-
- // Support: Android 4.0 only
- // Strict mode functions invoked without .call/.apply get global-object context
- updateFunc( i ).call( undefined, resolveValues[ i ] );
- }
- }
+ // Single- and empty arguments are adopted like Promise.resolve
+ if ( remaining <= 1 ) {
+ adoptValue( singleValue, master.resolve, master.reject );
- // If we're not waiting on anything, resolve the master
- } else {
- master.resolveWith();
+ // Use .then() to unwrap secondary thenables (cf. gh-3000)
+ return master.then();
}
+ // Multiple arguments are aggregated like Promise.all array elements
+ while ( i-- ) {
+ adoptValue( resolveValues[ i ], updateFunc( i ), master.reject );
+ }
return master.promise();
}
} );
} );
} );
-QUnit.test( "jQuery.when", function( assert ) {
- assert.expect( 37 );
+QUnit.test( "jQuery.when(nonThenable) - like Promise.resolve", function( assert ) {
+ "use strict";
+
+ assert.expect( 44 );
+
+ var
+
+ // Support: Android 4.0 only
+ // Strict mode functions invoked without .call/.apply get global-object context
+ defaultContext = (function getDefaultContext() { return this; }).call(),
+
+ done = assert.async( 20 );
+
+ jQuery.when()
+ .done( function( resolveValue ) {
+ assert.strictEqual( resolveValue, undefined, "Resolved .done with no arguments" );
+ assert.strictEqual( this, defaultContext, "Default .done context with no arguments" );
+ } )
+ .then( function( resolveValue ) {
+ assert.strictEqual( resolveValue, undefined, "Resolved .then with no arguments" );
+ assert.strictEqual( this, defaultContext, "Default .then context with no arguments" );
+ } );
- // Some other objects
jQuery.each( {
"an empty string": "",
"a non-empty string": "some string",
"a plain object": {},
"an array": [ 1, 2, 3 ]
}, function( message, value ) {
- assert.ok(
- jQuery.isFunction(
- jQuery.when( value ).done( function( resolveValue ) {
- assert.strictEqual( this, window, "Context is the global object with " + message );
- assert.strictEqual( resolveValue, value, "Test the promise was resolved with " + message );
- } ).promise
- ),
- "Test " + message + " triggers the creation of a new Promise"
- );
- } );
-
- assert.ok(
- jQuery.isFunction(
- jQuery.when().done( function( resolveValue ) {
- assert.strictEqual( this, window, "Test the promise was resolved with window as its context" );
- assert.strictEqual( resolveValue, undefined, "Test the promise was resolved with no parameter" );
- } ).promise
- ),
- "Test calling when with no parameter triggers the creation of a new Promise"
- );
-
- var cache,
- context = {};
-
- jQuery.when( jQuery.Deferred().resolveWith( context ) ).done( function() {
- assert.strictEqual( this, context, "when( promise ) propagates context" );
- } );
-
- jQuery.each( [ 1, 2, 3 ], function( k, i ) {
- jQuery.when( cache || jQuery.Deferred( function() {
- this.resolve( i );
- } )
- ).done( function( value ) {
- assert.strictEqual( value, 1, "Function executed" + ( i > 1 ? " only once" : "" ) );
- cache = value;
- } );
+ var code = "jQuery.when( " + message + " )",
+ onFulfilled = function( method ) {
+ var call = code + "." + method;
+ return function( resolveValue ) {
+ assert.strictEqual( resolveValue, value, call + " resolve" );
+ assert.strictEqual( this, defaultContext, call + " context" );
+ done();
+ };
+ },
+ onRejected = function( method ) {
+ var call = code + "." + method;
+ return function() {
+ assert.ok( false, call + " reject" );
+ done();
+ };
+ };
+ jQuery.when( value )
+ .done( onFulfilled( "done" ) )
+ .fail( onRejected( "done" ) )
+ .then( onFulfilled( "then" ), onRejected( "then" ) );
} );
} );
-QUnit.test( "jQuery.when - joined", function( assert ) {
+QUnit.test( "jQuery.when(thenable) - like Promise.resolve", function( assert ) {
+ "use strict";
+
+ assert.expect( 56 );
+
+ var slice = [].slice,
+ sentinel = { context: "explicit" },
+ eventuallyFulfilled = jQuery.Deferred().notify( true ),
+ eventuallyRejected = jQuery.Deferred().notify( true ),
+ inputs = {
+ promise: Promise.resolve( true ),
+ rejectedPromise: Promise.reject( false ),
+ deferred: jQuery.Deferred().resolve( true ),
+ eventuallyFulfilled: eventuallyFulfilled,
+ secondaryFulfilled: jQuery.Deferred().resolve( eventuallyFulfilled ),
+ 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 ),
+ multiRejectedDeferred: jQuery.Deferred().reject( "baz", "quux" ),
+ rejectedDeferredWith: jQuery.Deferred().rejectWith( sentinel, [ false ] ),
+ multiRejectedDeferredWith: jQuery.Deferred().rejectWith( sentinel, [ "baz", "quux" ] )
+ },
+ contexts = {
+ deferredWith: sentinel,
+ multiDeferredWith: sentinel,
+ rejectedDeferredWith: sentinel,
+ multiRejectedDeferredWith: sentinel
+ },
+ willSucceed = {
+ promise: [ true ],
+ deferred: [ true ],
+ eventuallyFulfilled: [ true ],
+ secondaryFulfilled: [ true ],
+ multiDeferred: [ "foo", "bar" ],
+ deferredWith: [ true ],
+ multiDeferredWith: [ "foo", "bar" ]
+ },
+ willError = {
+ rejectedPromise: [ false ],
+ rejectedDeferred: [ false ],
+ eventuallyRejected: [ false ],
+ secondaryRejected: [ false ],
+ multiRejectedDeferred: [ "baz", "quux" ],
+ rejectedDeferredWith: [ false ],
+ multiRejectedDeferredWith: [ "baz", "quux" ]
+ },
+
+ // Support: Android 4.0 only
+ // Strict mode functions invoked without .call/.apply get global-object context
+ defaultContext = (function getDefaultContext() { return this; }).call(),
+
+ done = assert.async( 28 );
+
+ jQuery.each( inputs, function( message, value ) {
+ var code = "jQuery.when( " + message + " )",
+ shouldResolve = willSucceed[ message ],
+ shouldError = willError[ message ],
+ context = contexts[ message ] || defaultContext,
+ onFulfilled = function( method ) {
+ var call = code + "." + method;
+ return function() {
+ if ( shouldResolve ) {
+ assert.deepEqual( slice.call( arguments ), shouldResolve,
+ call + " resolve" );
+ assert.strictEqual( this, context, call + " context" );
+ } else {
+ assert.ok( false, call + " resolve" );
+ }
+ done();
+ };
+ },
+ onRejected = function( method ) {
+ var call = code + "." + method;
+ return function() {
+ if ( shouldError ) {
+ assert.deepEqual( slice.call( arguments ), shouldError, call + " reject" );
+ assert.strictEqual( this, context, call + " context" );
+ } else {
+ assert.ok( false, call + " reject" );
+ }
+ done();
+ };
+ };
+
+ jQuery.when( value )
+ .done( onFulfilled( "done" ) )
+ .fail( onRejected( "done" ) )
+ .then( onFulfilled( "then" ), onRejected( "then" ) );
+ } );
+
+ setTimeout( function() {
+ eventuallyFulfilled.resolve( true );
+ eventuallyRejected.reject( false );
+ }, 50 );
+} );
- assert.expect( 81 );
+QUnit.test( "jQuery.when(a, b) - like Promise.all", function( assert ) {
+ "use strict";
- var deferreds = {
+ assert.expect( 196 );
+
+ var slice = [].slice,
+ deferreds = {
rawValue: 1,
fulfilled: jQuery.Deferred().resolve( 1 ),
rejected: jQuery.Deferred().reject( 0 ),
eventuallyRejected: true,
rejectedStandardPromise: true
},
- counter = 49,
// Support: Android 4.0 only
// Strict mode functions invoked without .call/.apply get global-object context
- expectedContext = (function() { "use strict"; return this; }).call();
-
- QUnit.stop();
+ defaultContext = (function getDefaultContext() { return this; }).call(),
- function restart() {
- if ( !--counter ) {
- QUnit.start();
- }
- }
+ done = assert.async( 98 );
- jQuery.each( deferreds, function( id1, defer1 ) {
- jQuery.each( deferreds, function( id2, defer2 ) {
- var shouldResolve = willSucceed[ id1 ] && willSucceed[ id2 ],
+ jQuery.each( deferreds, function( id1, v1 ) {
+ jQuery.each( deferreds, function( id2, v2 ) {
+ var code = "jQuery.when( " + id1 + ", " + id2 + " )",
+ shouldResolve = willSucceed[ id1 ] && willSucceed[ id2 ],
shouldError = willError[ id1 ] || willError[ id2 ],
- expected = shouldResolve ? [ 1, 1 ] : [ 0, undefined ],
- code = "jQuery.when( " + id1 + ", " + id2 + " )";
-
- jQuery.when( defer1, defer2 ).done( function( a, b ) {
- if ( shouldResolve ) {
- assert.deepEqual( [ a, b ], expected, code + " => resolve" );
- assert.strictEqual( this[ 0 ], expectedContext, code + " => context[0] OK" );
- assert.strictEqual( this[ 1 ], expectedContext, code + " => context[1] OK" );
- } else {
- assert.ok( false, code + " => resolve" );
- }
- } ).fail( function( a, b ) {
- if ( shouldError ) {
- assert.deepEqual( [ a, b ], expected, code + " => reject" );
- } else {
- assert.ok( false, code + " => reject" );
- }
- } ).always( restart );
+ expected = shouldResolve ? [ 1, 1 ] : [ 0 ],
+ context = shouldResolve ? [ defaultContext, defaultContext ] : defaultContext,
+ onFulfilled = function( method ) {
+ var call = code + "." + method;
+ return function() {
+ if ( shouldResolve ) {
+ assert.deepEqual( slice.call( arguments ), expected,
+ call + " resolve" );
+ assert.deepEqual( this, context, code + " context" );
+ } else {
+ assert.ok( false, call + " resolve" );
+ }
+ done();
+ };
+ },
+ onRejected = function( method ) {
+ var call = code + "." + method;
+ return function() {
+ if ( shouldError ) {
+ assert.deepEqual( slice.call( arguments ), expected, call + " reject" );
+ assert.deepEqual( this, context, code + " context" );
+ } else {
+ assert.ok( false, call + " reject" );
+ }
+ done();
+ };
+ };
+
+ jQuery.when( v1, v2 )
+ .done( onFulfilled( "done" ) )
+ .fail( onRejected( "done" ) )
+ .then( onFulfilled( "then" ), onRejected( "then" ) );
+ } );
+ } );
+
+ setTimeout( function() {
+ deferreds.eventuallyFulfilled.resolve( 1 );
+ deferreds.eventuallyRejected.reject( 0 );
+ }, 50 );
+} );
+
+QUnit.test( "jQuery.when - always returns a new promise", function( assert ) {
+
+ assert.expect( 42 );
+
+ jQuery.each( {
+ "no arguments": [],
+ "non-thenable": [ "foo" ],
+ "promise": [ Promise.resolve( "bar" ) ],
+ "rejected promise": [ Promise.reject( "bar" ) ],
+ "deferred": [ jQuery.Deferred().resolve( "baz" ) ],
+ "rejected deferred": [ jQuery.Deferred().reject( "baz" ) ],
+ "multi-resolved deferred": [ jQuery.Deferred().resolve( "qux", "quux" ) ],
+ "multiple non-thenables": [ "corge", "grault" ],
+ "multiple deferreds": [
+ jQuery.Deferred().resolve( "garply" ),
+ jQuery.Deferred().resolve( "waldo" )
+ ]
+ }, function( label, args ) {
+ var result = jQuery.when.apply( jQuery, args );
+
+ assert.ok( jQuery.isFunction( result.then ), "Thenable returned from " + label );
+ assert.strictEqual( result.resolve, undefined, "Non-deferred returned from " + label );
+ assert.strictEqual( result.promise(), result, "Promise returned from " + label );
+
+ jQuery.each( args, function( i, arg ) {
+ assert.notStrictEqual( result, arg, "Returns distinct from arg " + i + " of " + label );
+ if ( arg.promise ) {
+ assert.notStrictEqual( result, arg.promise(),
+ "Returns distinct from promise of arg " + i + " of " + label );
+ }
} );
} );
- deferreds.eventuallyFulfilled.resolve( 1 );
- deferreds.eventuallyRejected.reject( 0 );
} );
QUnit.test( "jQuery.when - notify does not affect resolved", function( assert ) {
assert.ok( false, "Error on resolve" );
} );
} );
-
-QUnit.test( "jQuery.when - filtering", function( assert ) {
-
- assert.expect( 2 );
-
- function increment( x ) {
- return x + 1;
- }
-
- QUnit.stop();
-
- jQuery.when(
- jQuery.Deferred().resolve( 3 ).then( increment ),
- jQuery.Deferred().reject( 5 ).then( null, increment )
- ).done( function( four, six ) {
- assert.strictEqual( four, 4, "resolved value incremented" );
- assert.strictEqual( six, 6, "rejected value incremented" );
- QUnit.start();
- } );
-} );
-
-QUnit.test( "jQuery.when - exceptions", function( assert ) {
-
- assert.expect( 2 );
-
- function woops() {
- throw "exception thrown";
- }
-
- QUnit.stop();
-
- jQuery.Deferred().resolve().then( woops ).fail( function( doneException ) {
- assert.strictEqual( doneException, "exception thrown", "throwing in done handler" );
- jQuery.Deferred().reject().then( null, woops ).fail( function( failException ) {
- assert.strictEqual( failException, "exception thrown", "throwing in fail handler" );
- QUnit.start();
- } );
- } );
-} );
-
-QUnit.test( "jQuery.when - chaining", function( assert ) {
-
- assert.expect( 4 );
-
- var defer = jQuery.Deferred();
-
- function chain() {
- return defer;
- }
-
- function chainStandard() {
- return Promise.resolve( "std deferred" );
- }
-
- QUnit.stop();
-
- jQuery.when(
- jQuery.Deferred().resolve( 3 ).then( chain ),
- jQuery.Deferred().reject( 5 ).then( null, chain ),
- jQuery.Deferred().resolve( 3 ).then( chainStandard ),
- jQuery.Deferred().reject( 5 ).then( null, chainStandard )
- ).done( function( v1, v2, s1, s2 ) {
- assert.strictEqual( v1, "other deferred", "chaining in done handler" );
- assert.strictEqual( v2, "other deferred", "chaining in fail handler" );
- assert.strictEqual( s1, "std deferred", "chaining thenable in done handler" );
- assert.strictEqual( s2, "std deferred", "chaining thenable in fail handler" );
- QUnit.start();
- } );
-
- defer.resolve( "other deferred" );
-} );
-
-QUnit.test( "jQuery.when - solitary thenables", function( assert ) {
-
- assert.expect( 1 );
-
- var done = assert.async(),
- rejected = new Promise( function( resolve, reject ) {
- setTimeout( function() {
- reject( "rejected" );
- }, 100 );
- } );
-
- jQuery.when( rejected ).then(
- function() {
- assert.ok( false, "Rejected, solitary, non-Deferred thenable should not resolve" );
- done();
- },
- function() {
- assert.ok( true, "Rejected, solitary, non-Deferred thenable rejected properly" );
- done();
- }
- );
-} );
-
-QUnit.test( "jQuery.when does not reuse a solitary jQuery Deferred (gh-2018)", function( assert ) {
-
- assert.expect( 2 );
- var defer = jQuery.Deferred().resolve(),
- promise = jQuery.when( defer );
-
- assert.equal( promise.state(), "resolved", "Master Deferred is immediately resolved" );
- assert.notStrictEqual( defer.promise(), promise, "jQuery.when returns the master deferred's promise" );
-} );