Offset: allow offset setter to throw for disconnected elements
[jquery.git] / src / deferred.js
blobcdb7f1cc4e7c9efbc2f0c519892f822e72f249fd
1 define([
2         "./core",
3         "./var/slice",
4         "./callbacks"
5 ], function( jQuery, slice ) {
7 function Identity( v ) {
8         return v;
10 function Thrower( ex ) {
11         throw ex;
14 jQuery.extend({
16         Deferred: function( func ) {
17                 var tuples = [
18                                 // action, add listener, callbacks,
19                                 // ... .then handlers, argument index, [final state]
20                                 [ "notify", "progress", jQuery.Callbacks("memory"),
21                                         jQuery.Callbacks("memory"), 2 ],
22                                 [ "resolve", "done", jQuery.Callbacks("once memory"),
23                                         jQuery.Callbacks("once memory"), 0, "resolved" ],
24                                 [ "reject", "fail", jQuery.Callbacks("once memory"),
25                                         jQuery.Callbacks("once memory"), 1, "rejected" ]
26                         ],
27                         state = "pending",
28                         promise = {
29                                 state: function() {
30                                         return state;
31                                 },
32                                 always: function() {
33                                         deferred.done( arguments ).fail( arguments );
34                                         return this;
35                                 },
36                                 // Keep pipe for back-compat
37                                 pipe: function( /* fnDone, fnFail, fnProgress */ ) {
38                                         var fns = arguments;
40                                         return jQuery.Deferred(function( newDefer ) {
41                                                 jQuery.each( tuples, function( i, tuple ) {
42                                                         // Map tuples (progress, done, fail) to arguments (done, fail, progress)
43                                                         var fn = jQuery.isFunction( fns[ tuple[ 4 ] ] ) && fns[ tuple[ 4 ] ];
45                                                         // deferred.progress(function() { bind to newDefer or newDefer.notify })
46                                                         // deferred.done(function() { bind to newDefer or newDefer.resolve })
47                                                         // deferred.fail(function() { bind to newDefer or newDefer.reject })
48                                                         deferred[ tuple[1] ](function() {
49                                                                 var returned = fn && fn.apply( this, arguments );
50                                                                 if ( returned && jQuery.isFunction( returned.promise ) ) {
51                                                                         returned.promise()
52                                                                                 .progress( newDefer.notify )
53                                                                                 .done( newDefer.resolve )
54                                                                                 .fail( newDefer.reject );
55                                                                 } else {
56                                                                         newDefer[ tuple[ 0 ] + "With" ](
57                                                                                 this === promise ? newDefer.promise() : this,
58                                                                                 fn ? [ returned ] : arguments
59                                                                         );
60                                                                 }
61                                                         });
62                                                 });
63                                                 fns = null;
64                                         }).promise();
65                                 },
66                                 then: function( onFulfilled, onRejected, onProgress ) {
67                                         var maxDepth = 0;
68                                         function resolve( depth, deferred, handler, special ) {
69                                                 return function() {
70                                                         var that = this === promise ? undefined : this,
71                                                                 args = arguments,
72                                                                 mightThrow = function() {
73                                                                         var returned, then;
75                                                                         // Support: Promises/A+ section 2.3.3.3.3
76                                                                         // https://promisesaplus.com/#point-59
77                                                                         // Ignore double-resolution attempts
78                                                                         if ( depth < maxDepth ) {
79                                                                                 return;
80                                                                         }
82                                                                         returned = handler.apply( that, args );
84                                                                         // Support: Promises/A+ section 2.3.1
85                                                                         // https://promisesaplus.com/#point-48
86                                                                         if ( returned === deferred.promise() ) {
87                                                                                 throw new TypeError( "Thenable self-resolution" );
88                                                                         }
90                                                                         // Support: Promises/A+ sections 2.3.3.1, 3.5
91                                                                         // https://promisesaplus.com/#point-54
92                                                                         // https://promisesaplus.com/#point-75
93                                                                         // Retrieve `then` only once
94                                                                         then = returned &&
96                                                                                 // Support: Promises/A+ section 2.3.4
97                                                                                 // https://promisesaplus.com/#point-64
98                                                                                 // Only check objects and functions for thenability
99                                                                                 ( typeof returned === "object" ||
100                                                                                         typeof returned === "function" ) &&
101                                                                                 returned.then;
103                                                                         // Handle a returned thenable
104                                                                         if ( jQuery.isFunction( then ) ) {
105                                                                                 // Special processors (notify) just wait for resolution
106                                                                                 if ( special ) {
107                                                                                         then.call(
108                                                                                                 returned,
109                                                                                                 resolve( maxDepth, deferred, Identity, special ),
110                                                                                                 resolve( maxDepth, deferred, Thrower, special )
111                                                                                         );
113                                                                                 // Normal processors (resolve) also hook into progress
114                                                                                 } else {
116                                                                                         // ...and disregard older resolution values
117                                                                                         maxDepth++;
119                                                                                         then.call(
120                                                                                                 returned,
121                                                                                                 resolve( maxDepth, deferred, Identity, special ),
122                                                                                                 resolve( maxDepth, deferred, Thrower, special ),
123                                                                                                 resolve( maxDepth, deferred, Identity,
124                                                                                                         deferred.notify )
125                                                                                         );
126                                                                                 }
128                                                                         // Handle all other returned values
129                                                                         } else {
130                                                                                 // Only substitue handlers pass on context
131                                                                                 // and multiple values (non-spec behavior)
132                                                                                 if ( handler !== Identity ) {
133                                                                                         that = undefined;
134                                                                                         args = [ returned ];
135                                                                                 }
137                                                                                 // Process the value(s)
138                                                                                 // Default process is resolve
139                                                                                 ( special || deferred.resolveWith )(
140                                                                                         that || deferred.promise(), args );
141                                                                         }
142                                                                 },
144                                                                 // Only normal processors (resolve) catch and reject exceptions
145                                                                 process = special ?
146                                                                         mightThrow :
147                                                                         function() {
148                                                                                 try {
149                                                                                         mightThrow();
150                                                                                 } catch ( e ) {
152                                                                                         // Support: Promises/A+ section 2.3.3.3.4.1
153                                                                                         // https://promisesaplus.com/#point-61
154                                                                                         // Ignore post-resolution exceptions
155                                                                                         if ( depth + 1 >= maxDepth ) {
156                                                                                                 // Only substitue handlers pass on context
157                                                                                                 // and multiple values (non-spec behavior)
158                                                                                                 if ( handler !== Thrower ) {
159                                                                                                         that = undefined;
160                                                                                                         args = [ e ];
161                                                                                                 }
163                                                                                                 deferred.rejectWith( that || deferred.promise(),
164                                                                                                         args );
165                                                                                         }
166                                                                                 }
167                                                                         };
169                                                         // Support: Promises/A+ section 2.3.3.3.1
170                                                         // https://promisesaplus.com/#point-57
171                                                         // Re-resolve promises immediately to dodge false rejection from
172                                                         // subsequent errors
173                                                         if ( depth ) {
174                                                                 process();
175                                                         } else {
176                                                                 setTimeout( process );
177                                                         }
178                                                 };
179                                         }
181                                         return jQuery.Deferred(function( newDefer ) {
182                                                 // progress_handlers.add( ... )
183                                                 tuples[ 0 ][ 3 ].add(
184                                                         resolve(
185                                                                 0,
186                                                                 newDefer,
187                                                                 jQuery.isFunction( onProgress ) ?
188                                                                         onProgress :
189                                                                         Identity,
190                                                                 newDefer.notifyWith
191                                                         )
192                                                 );
194                                                 // fulfilled_handlers.add( ... )
195                                                 tuples[ 1 ][ 3 ].add(
196                                                         resolve(
197                                                                 0,
198                                                                 newDefer,
199                                                                 jQuery.isFunction( onFulfilled ) ?
200                                                                         onFulfilled :
201                                                                         Identity
202                                                         )
203                                                 );
205                                                 // rejected_handlers.add( ... )
206                                                 tuples[ 2 ][ 3 ].add(
207                                                         resolve(
208                                                                 0,
209                                                                 newDefer,
210                                                                 jQuery.isFunction( onRejected ) ?
211                                                                         onRejected :
212                                                                         Thrower
213                                                         )
214                                                 );
215                                         }).promise();
216                                 },
217                                 // Get a promise for this deferred
218                                 // If obj is provided, the promise aspect is added to the object
219                                 promise: function( obj ) {
220                                         return obj != null ? jQuery.extend( obj, promise ) : promise;
221                                 }
222                         },
223                         deferred = {};
225                 // Add list-specific methods
226                 jQuery.each( tuples, function( i, tuple ) {
227                         var list = tuple[ 2 ],
228                                 stateString = tuple[ 5 ];
230                         // promise.progress = list.add
231                         // promise.done = list.add
232                         // promise.fail = list.add
233                         promise[ tuple[1] ] = list.add;
235                         // Handle state
236                         if ( stateString ) {
237                                 list.add(
238                                         function() {
239                                                 // state = "resolved" (i.e., fulfilled)
240                                                 // state = "rejected"
241                                                 state = stateString;
242                                         },
244                                         // rejected_callbacks.disable
245                                         // fulfilled_callbacks.disable
246                                         tuples[ 3 - i ][ 2 ].disable,
248                                         // progress_callbacks.lock
249                                         tuples[ 0 ][ 2 ].lock
250                                 );
251                         }
253                         // progress_handlers.fire
254                         // fulfilled_handlers.fire
255                         // rejected_handlers.fire
256                         list.add( tuple[ 3 ].fire );
258                         // deferred.notify = function() { deferred.notifyWith(...) }
259                         // deferred.resolve = function() { deferred.resolveWith(...) }
260                         // deferred.reject = function() { deferred.rejectWith(...) }
261                         deferred[ tuple[0] ] = function() {
262                                 deferred[ tuple[0] + "With" ]( this === deferred ? promise : this, arguments );
263                                 return this;
264                         };
266                         // deferred.notifyWith = list.fireWith
267                         // deferred.resolveWith = list.fireWith
268                         // deferred.rejectWith = list.fireWith
269                         deferred[ tuple[0] + "With" ] = list.fireWith;
270                 });
272                 // Make the deferred a promise
273                 promise.promise( deferred );
275                 // Call given func if any
276                 if ( func ) {
277                         func.call( deferred, deferred );
278                 }
280                 // All done!
281                 return deferred;
282         },
284         // Deferred helper
285         when: function( subordinate /* , ..., subordinateN */ ) {
286                 var method,
287                         i = 0,
288                         resolveValues = slice.call( arguments ),
289                         length = resolveValues.length,
291                         // the count of uncompleted subordinates
292                         remaining = length !== 1 ||
293                                 ( subordinate && jQuery.isFunction( subordinate.promise ) ) ? length : 0,
295                         // the master Deferred.
296                         // If resolveValues consist of only a single Deferred, just use that.
297                         master = remaining === 1 ? subordinate : jQuery.Deferred(),
299                         // Update function for both resolve and progress values
300                         updateFunc = function( i, contexts, values ) {
301                                 return function( value ) {
302                                         contexts[ i ] = this;
303                                         values[ i ] = arguments.length > 1 ? slice.call( arguments ) : value;
304                                         if ( values === progressValues ) {
305                                                 master.notifyWith( contexts, values );
306                                         } else if ( !( --remaining ) ) {
307                                                 master.resolveWith( contexts, values );
308                                         }
309                                 };
310                         },
311                         progressValues, progressContexts, resolveContexts;
313                 // Add listeners to Deferred subordinates; treat others as resolved
314                 if ( length > 1 ) {
315                         progressValues = new Array( length );
316                         progressContexts = new Array( length );
317                         resolveContexts = new Array( length );
318                         for ( ; i < length; i++ ) {
319                                 if ( resolveValues[ i ] &&
320                                         jQuery.isFunction( (method = resolveValues[ i ].promise) ) ) {
322                                         method.call( resolveValues[ i ] )
323                                                 .progress( updateFunc( i, progressContexts, progressValues ) )
324                                                 .done( updateFunc( i, resolveContexts, resolveValues ) )
325                                                 .fail( master.reject );
326                                 } else if ( resolveValues[ i ] &&
327                                         jQuery.isFunction( (method = resolveValues[ i ].then) ) ) {
329                                         method.call(
330                                                 resolveValues[ i ],
331                                                 updateFunc( i, resolveContexts, resolveValues ),
332                                                 master.reject,
333                                                 updateFunc( i, progressContexts, progressValues )
334                                         );
335                                 } else {
336                                         --remaining;
337                                 }
338                         }
339                 }
341                 // If we're not waiting on anything, resolve the master
342                 if ( !remaining ) {
343                         master.resolveWith( resolveContexts, resolveValues );
344                 }
346                 return master.promise();
347         }
350 return jQuery;