Permalink
Browse files

Deferred: Separate the two paths in jQuery.when

Single- and no-argument calls act like Promise.resolve.
Multi-argument calls act like Promise.all.

Fixes gh-3029
Closes gh-3059
  • Loading branch information...
1 parent 0bd98b1 commit 356a3bccb0e7468a2c8ce7d8c9c6cd0c5d436b8b @gibson042 gibson042 committed Apr 15, 2016
@@ -4,10 +4,20 @@ module.exports = function( grunt ) {
var spawnTest = require( "./lib/spawn_test.js" );
- grunt.registerTask( "promises_aplus_tests", function() {
+ grunt.registerTask( "promises_aplus_tests",
+ [ "promises_aplus_tests_deferred", "promises_aplus_tests_when" ] );
+
+ grunt.registerTask( "promises_aplus_tests_deferred", function() {
+ spawnTest( this.async(),
+ "./node_modules/.bin/promises-aplus-tests",
+ "test/promises_aplus_adapter_deferred.js"
+ );
+ } );
+
+ grunt.registerTask( "promises_aplus_tests_when", function() {
spawnTest( this.async(),
"./node_modules/.bin/promises-aplus-tests",
- "test/promises_aplus_adapter.js"
+ "test/promises_aplus_adapter_when.js"
);
} );
};
View
@@ -13,6 +13,38 @@ function Thrower( ex ) {
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 ) {
@@ -305,67 +337,45 @@ jQuery.extend( {
},
// 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();
}
} );
@@ -0,0 +1,51 @@
+/* jshint node: true */
+
+"use strict";
+
+require( "jsdom" ).env( "", function( errors, window ) {
+ if ( errors ) {
+ console.error( errors );
+ return;
+ }
+
+ var jQuery = require( ".." )( window );
+
+ exports.deferred = function() {
+ var adopted, promised,
+ obj = {
+ resolve: function() {
+ if ( !adopted ) {
+ adopted = jQuery.when.apply( jQuery, arguments );
+ if ( promised ) {
+ adopted.then( promised.resolve, promised.reject );
+ }
+ }
+ return adopted;
+ },
+ reject: function( value ) {
+ if ( !adopted ) {
+ adopted = jQuery.when( jQuery.Deferred().reject( value ) );
+ if ( promised ) {
+ adopted.then( promised.resolve, promised.reject );
+ }
+ }
+ return adopted;
+ },
+
+ // A manually-constructed thenable that works even if calls precede resolve/reject
+ promise: {
+ then: function() {
+ if ( !adopted ) {
+ if ( !promised ) {
+ promised = jQuery.Deferred();
+ }
+ return promised.then.apply( promised, arguments );
+ }
+ return adopted.then.apply( adopted, arguments );
+ }
+ }
+ };
+
+ return obj;
+ };
+} );
Oops, something went wrong.

0 comments on commit 356a3bc

Please sign in to comment.