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...
gibson042 committed Apr 15, 2016
1 parent 0bd98b1 commit 356a3bccb0e7468a2c8ce7d8c9c6cd0c5d436b8b
@@ -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.