deferred.js 10 KB

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