deferred.js (11376B)
1 define( [ 2 "./core", 3 "./var/isFunction", 4 "./var/slice", 5 "./callbacks" 6 ], function( jQuery, isFunction, slice ) { 7 8 "use strict"; 9 10 function Identity( v ) { 11 return v; 12 } 13 function Thrower( ex ) { 14 throw ex; 15 } 16 17 function adoptValue( value, resolve, reject, noValue ) { 18 var method; 19 20 try { 21 22 // Check for promise aspect first to privilege synchronous behavior 23 if ( value && isFunction( ( method = value.promise ) ) ) { 24 method.call( value ).done( resolve ).fail( reject ); 25 26 // Other thenables 27 } else if ( value && isFunction( ( method = value.then ) ) ) { 28 method.call( value, resolve, reject ); 29 30 // Other non-thenables 31 } else { 32 33 // Control `resolve` arguments by letting Array#slice cast boolean `noValue` to integer: 34 // * false: [ value ].slice( 0 ) => resolve( value ) 35 // * true: [ value ].slice( 1 ) => resolve() 36 resolve.apply( undefined, [ value ].slice( noValue ) ); 37 } 38 39 // For Promises/A+, convert exceptions into rejections 40 // Since jQuery.when doesn't unwrap thenables, we can skip the extra checks appearing in 41 // Deferred#then to conditionally suppress rejection. 42 } catch ( value ) { 43 44 // Support: Android 4.0 only 45 // Strict mode functions invoked without .call/.apply get global-object context 46 reject.apply( undefined, [ value ] ); 47 } 48 } 49 50 jQuery.extend( { 51 52 Deferred: function( func ) { 53 var tuples = [ 54 55 // action, add listener, callbacks, 56 // ... .then handlers, argument index, [final state] 57 [ "notify", "progress", jQuery.Callbacks( "memory" ), 58 jQuery.Callbacks( "memory" ), 2 ], 59 [ "resolve", "done", jQuery.Callbacks( "once memory" ), 60 jQuery.Callbacks( "once memory" ), 0, "resolved" ], 61 [ "reject", "fail", jQuery.Callbacks( "once memory" ), 62 jQuery.Callbacks( "once memory" ), 1, "rejected" ] 63 ], 64 state = "pending", 65 promise = { 66 state: function() { 67 return state; 68 }, 69 always: function() { 70 deferred.done( arguments ).fail( arguments ); 71 return this; 72 }, 73 "catch": function( fn ) { 74 return promise.then( null, fn ); 75 }, 76 77 // Keep pipe for back-compat 78 pipe: function( /* fnDone, fnFail, fnProgress */ ) { 79 var fns = arguments; 80 81 return jQuery.Deferred( function( newDefer ) { 82 jQuery.each( tuples, function( _i, tuple ) { 83 84 // Map tuples (progress, done, fail) to arguments (done, fail, progress) 85 var fn = isFunction( fns[ tuple[ 4 ] ] ) && fns[ tuple[ 4 ] ]; 86 87 // deferred.progress(function() { bind to newDefer or newDefer.notify }) 88 // deferred.done(function() { bind to newDefer or newDefer.resolve }) 89 // deferred.fail(function() { bind to newDefer or newDefer.reject }) 90 deferred[ tuple[ 1 ] ]( function() { 91 var returned = fn && fn.apply( this, arguments ); 92 if ( returned && isFunction( returned.promise ) ) { 93 returned.promise() 94 .progress( newDefer.notify ) 95 .done( newDefer.resolve ) 96 .fail( newDefer.reject ); 97 } else { 98 newDefer[ tuple[ 0 ] + "With" ]( 99 this, 100 fn ? [ returned ] : arguments 101 ); 102 } 103 } ); 104 } ); 105 fns = null; 106 } ).promise(); 107 }, 108 then: function( onFulfilled, onRejected, onProgress ) { 109 var maxDepth = 0; 110 function resolve( depth, deferred, handler, special ) { 111 return function() { 112 var that = this, 113 args = arguments, 114 mightThrow = function() { 115 var returned, then; 116 117 // Support: Promises/A+ section 2.3.3.3.3 118 // https://promisesaplus.com/#point-59 119 // Ignore double-resolution attempts 120 if ( depth < maxDepth ) { 121 return; 122 } 123 124 returned = handler.apply( that, args ); 125 126 // Support: Promises/A+ section 2.3.1 127 // https://promisesaplus.com/#point-48 128 if ( returned === deferred.promise() ) { 129 throw new TypeError( "Thenable self-resolution" ); 130 } 131 132 // Support: Promises/A+ sections 2.3.3.1, 3.5 133 // https://promisesaplus.com/#point-54 134 // https://promisesaplus.com/#point-75 135 // Retrieve `then` only once 136 then = returned && 137 138 // Support: Promises/A+ section 2.3.4 139 // https://promisesaplus.com/#point-64 140 // Only check objects and functions for thenability 141 ( typeof returned === "object" || 142 typeof returned === "function" ) && 143 returned.then; 144 145 // Handle a returned thenable 146 if ( isFunction( then ) ) { 147 148 // Special processors (notify) just wait for resolution 149 if ( special ) { 150 then.call( 151 returned, 152 resolve( maxDepth, deferred, Identity, special ), 153 resolve( maxDepth, deferred, Thrower, special ) 154 ); 155 156 // Normal processors (resolve) also hook into progress 157 } else { 158 159 // ...and disregard older resolution values 160 maxDepth++; 161 162 then.call( 163 returned, 164 resolve( maxDepth, deferred, Identity, special ), 165 resolve( maxDepth, deferred, Thrower, special ), 166 resolve( maxDepth, deferred, Identity, 167 deferred.notifyWith ) 168 ); 169 } 170 171 // Handle all other returned values 172 } else { 173 174 // Only substitute handlers pass on context 175 // and multiple values (non-spec behavior) 176 if ( handler !== Identity ) { 177 that = undefined; 178 args = [ returned ]; 179 } 180 181 // Process the value(s) 182 // Default process is resolve 183 ( special || deferred.resolveWith )( that, args ); 184 } 185 }, 186 187 // Only normal processors (resolve) catch and reject exceptions 188 process = special ? 189 mightThrow : 190 function() { 191 try { 192 mightThrow(); 193 } catch ( e ) { 194 195 if ( jQuery.Deferred.exceptionHook ) { 196 jQuery.Deferred.exceptionHook( e, 197 process.error ); 198 } 199 200 // Support: Promises/A+ section 2.3.3.3.4.1 201 // https://promisesaplus.com/#point-61 202 // Ignore post-resolution exceptions 203 if ( depth + 1 >= maxDepth ) { 204 205 // Only substitute handlers pass on context 206 // and multiple values (non-spec behavior) 207 if ( handler !== Thrower ) { 208 that = undefined; 209 args = [ e ]; 210 } 211 212 deferred.rejectWith( that, args ); 213 } 214 } 215 }; 216 217 // Support: Promises/A+ section 2.3.3.3.1 218 // https://promisesaplus.com/#point-57 219 // Re-resolve promises immediately to dodge false rejection from 220 // subsequent errors 221 if ( depth ) { 222 process(); 223 } else { 224 225 // Call an optional hook to record the error, in case of exception 226 // since it's otherwise lost when execution goes async 227 if ( jQuery.Deferred.getErrorHook ) { 228 process.error = jQuery.Deferred.getErrorHook(); 229 230 // The deprecated alias of the above. While the name suggests 231 // returning the stack, not an error instance, jQuery just passes 232 // it directly to `console.warn` so both will work; an instance 233 // just better cooperates with source maps. 234 } else if ( jQuery.Deferred.getStackHook ) { 235 process.error = jQuery.Deferred.getStackHook(); 236 } 237 window.setTimeout( process ); 238 } 239 }; 240 } 241 242 return jQuery.Deferred( function( newDefer ) { 243 244 // progress_handlers.add( ... ) 245 tuples[ 0 ][ 3 ].add( 246 resolve( 247 0, 248 newDefer, 249 isFunction( onProgress ) ? 250 onProgress : 251 Identity, 252 newDefer.notifyWith 253 ) 254 ); 255 256 // fulfilled_handlers.add( ... ) 257 tuples[ 1 ][ 3 ].add( 258 resolve( 259 0, 260 newDefer, 261 isFunction( onFulfilled ) ? 262 onFulfilled : 263 Identity 264 ) 265 ); 266 267 // rejected_handlers.add( ... ) 268 tuples[ 2 ][ 3 ].add( 269 resolve( 270 0, 271 newDefer, 272 isFunction( onRejected ) ? 273 onRejected : 274 Thrower 275 ) 276 ); 277 } ).promise(); 278 }, 279 280 // Get a promise for this deferred 281 // If obj is provided, the promise aspect is added to the object 282 promise: function( obj ) { 283 return obj != null ? jQuery.extend( obj, promise ) : promise; 284 } 285 }, 286 deferred = {}; 287 288 // Add list-specific methods 289 jQuery.each( tuples, function( i, tuple ) { 290 var list = tuple[ 2 ], 291 stateString = tuple[ 5 ]; 292 293 // promise.progress = list.add 294 // promise.done = list.add 295 // promise.fail = list.add 296 promise[ tuple[ 1 ] ] = list.add; 297 298 // Handle state 299 if ( stateString ) { 300 list.add( 301 function() { 302 303 // state = "resolved" (i.e., fulfilled) 304 // state = "rejected" 305 state = stateString; 306 }, 307 308 // rejected_callbacks.disable 309 // fulfilled_callbacks.disable 310 tuples[ 3 - i ][ 2 ].disable, 311 312 // rejected_handlers.disable 313 // fulfilled_handlers.disable 314 tuples[ 3 - i ][ 3 ].disable, 315 316 // progress_callbacks.lock 317 tuples[ 0 ][ 2 ].lock, 318 319 // progress_handlers.lock 320 tuples[ 0 ][ 3 ].lock 321 ); 322 } 323 324 // progress_handlers.fire 325 // fulfilled_handlers.fire 326 // rejected_handlers.fire 327 list.add( tuple[ 3 ].fire ); 328 329 // deferred.notify = function() { deferred.notifyWith(...) } 330 // deferred.resolve = function() { deferred.resolveWith(...) } 331 // deferred.reject = function() { deferred.rejectWith(...) } 332 deferred[ tuple[ 0 ] ] = function() { 333 deferred[ tuple[ 0 ] + "With" ]( this === deferred ? undefined : this, arguments ); 334 return this; 335 }; 336 337 // deferred.notifyWith = list.fireWith 338 // deferred.resolveWith = list.fireWith 339 // deferred.rejectWith = list.fireWith 340 deferred[ tuple[ 0 ] + "With" ] = list.fireWith; 341 } ); 342 343 // Make the deferred a promise 344 promise.promise( deferred ); 345 346 // Call given func if any 347 if ( func ) { 348 func.call( deferred, deferred ); 349 } 350 351 // All done! 352 return deferred; 353 }, 354 355 // Deferred helper 356 when: function( singleValue ) { 357 var 358 359 // count of uncompleted subordinates 360 remaining = arguments.length, 361 362 // count of unprocessed arguments 363 i = remaining, 364 365 // subordinate fulfillment data 366 resolveContexts = Array( i ), 367 resolveValues = slice.call( arguments ), 368 369 // the primary Deferred 370 primary = jQuery.Deferred(), 371 372 // subordinate callback factory 373 updateFunc = function( i ) { 374 return function( value ) { 375 resolveContexts[ i ] = this; 376 resolveValues[ i ] = arguments.length > 1 ? slice.call( arguments ) : value; 377 if ( !( --remaining ) ) { 378 primary.resolveWith( resolveContexts, resolveValues ); 379 } 380 }; 381 }; 382 383 // Single- and empty arguments are adopted like Promise.resolve 384 if ( remaining <= 1 ) { 385 adoptValue( singleValue, primary.done( updateFunc( i ) ).resolve, primary.reject, 386 !remaining ); 387 388 // Use .then() to unwrap secondary thenables (cf. gh-3000) 389 if ( primary.state() === "pending" || 390 isFunction( resolveValues[ i ] && resolveValues[ i ].then ) ) { 391 392 return primary.then(); 393 } 394 } 395 396 // Multiple arguments are aggregated like Promise.all array elements 397 while ( i-- ) { 398 adoptValue( resolveValues[ i ], updateFunc( i ), primary.reject ); 399 } 400 401 return primary.promise(); 402 } 403 } ); 404 405 return jQuery; 406 } );