deferred.js 11 KB

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