diff options
Diffstat (limited to 'src/deferred.js')
-rw-r--r-- | src/deferred.js | 100 |
1 files changed, 55 insertions, 45 deletions
diff --git a/src/deferred.js b/src/deferred.js index 0ea1e7f1f..6e4d43b31 100644 --- a/src/deferred.js +++ b/src/deferred.js @@ -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(); } } ); |