deferred.js 11 KB

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