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