Know when to hold em, know when to fold em.
[jquery.git] / src / deferred.js
blobd842065704e201d41a608f91934364d1a4d2f74c
1 jQuery.extend({
3         Deferred: function( func ) {
4                 var tuples = [
5                                 // action, add listener, listener list, final state
6                                 [ "resolve", "done", jQuery.Callbacks("once memory"), "resolved" ],
7                                 [ "reject", "fail", jQuery.Callbacks("once memory"), "rejected" ],
8                                 [ "notify", "progress", jQuery.Callbacks("memory") ]
9                         ],
10                         state = "pending",
11                         promise = {
12                                 state: function() {
13                                         return state;
14                                 },
15                                 always: function() {
16                                         deferred.done( arguments ).fail( arguments );
17                                         return this;
18                                 },
19                                 then: function( /* fnDone, fnFail, fnProgress */ ) {
20                                         var fns = arguments;
21                                         return jQuery.Deferred(function( newDefer ) {
22                                                 jQuery.each( tuples, function( i, tuple ) {
23                                                         var action = tuple[ 0 ],
24                                                                 fn = fns[ i ];
25                                                         // deferred[ done | fail | progress ] for forwarding actions to newDefer
26                                                         deferred[ tuple[1] ]( jQuery.isFunction( fn ) ?
27                                                                 function() {
28                                                                         var returned = fn.apply( this, arguments );
29                                                                         if ( returned && jQuery.isFunction( returned.promise ) ) {
30                                                                                 returned.promise()
31                                                                                         .done( newDefer.resolve )
32                                                                                         .fail( newDefer.reject )
33                                                                                         .progress( newDefer.notify );
34                                                                         } else {
35                                                                                 newDefer[ action + "With" ]( this === promise ? newDefer.promise() : this, [ returned ] );
36                                                                         }
37                                                                 } :
38                                                                 newDefer[ action ]
39                                                         );
40                                                 });
41                                                 fns = null;
42                                         }).promise();
43                                 },
44                                 // Get a promise for this deferred
45                                 // If obj is provided, the promise aspect is added to the object
46                                 promise: function( obj ) {
47                                         return obj != null ? jQuery.extend( obj, promise ) : promise;
48                                 }
49                         },
50                         deferred = {};
52                 // Keep pipe for back-compat
53                 promise.pipe = promise.then;
55                 // Add list-specific methods
56                 jQuery.each( tuples, function( i, tuple ) {
57                         var list = tuple[ 2 ],
58                                 stateString = tuple[ 3 ];
60                         // promise[ done | fail | progress ] = list.add
61                         promise[ tuple[1] ] = list.add;
63                         // Handle state
64                         if ( stateString ) {
65                                 list.add(function() {
66                                         // state = [ resolved | rejected ]
67                                         state = stateString;
69                                 // [ reject_list | resolve_list ].disable; progress_list.lock
70                                 }, tuples[ i ^ 1 ][ 2 ].disable, tuples[ 2 ][ 2 ].lock );
71                         }
73                         // deferred[ resolve | reject | notify ]
74                         deferred[ tuple[0] ] = function() {
75                                 deferred[ tuple[0] + "With" ]( promise, arguments );
76                                 return this;
77                         };
78                         deferred[ tuple[0] + "With" ] = list.fireWith;
79                 });
81                 // Make the deferred a promise
82                 promise.promise( deferred );
84                 // Call given func if any
85                 if ( func ) {
86                         func.call( deferred, deferred );
87                 }
89                 // All done!
90                 return deferred;
91         },
93         // Deferred helper
94         when: function( subordinate /* , ..., subordinateN */ ) {
95                 var i = 0,
96                         resolveValues = core_slice.call( arguments ),
97                         length = resolveValues.length,
99                         // the count of uncompleted subordinates
100                         remaining = length !== 1 || ( subordinate && jQuery.isFunction( subordinate.promise ) ) ? length : 0,
102                         // the master Deferred. If resolveValues consist of only a single Deferred, just use that.
103                         deferred = remaining === 1 ? subordinate : jQuery.Deferred(),
105                         // Update function for both resolve and progress values
106                         updateFunc = function( i, contexts, values ) {
107                                 return function( value ) {
108                                         contexts[ i ] = this;
109                                         values[ i ] = arguments.length > 1 ? core_slice.call( arguments ) : value;
110                                         if( values === progressValues ) {
111                                                 deferred.notifyWith( contexts, values );
112                                         } else if ( !( --remaining ) ) {
113                                                 deferred.resolveWith( contexts, values );
114                                         }
115                                 };
116                         },
118                         progressValues, progressContexts, resolveContexts;
120                 // add listeners to Deferred subordinates; treat others as resolved
121                 if ( length > 1 ) {
122                         progressValues = new Array( length );
123                         progressContexts = new Array( length );
124                         resolveContexts = new Array( length );
125                         for ( ; i < length; i++ ) {
126                                 if ( resolveValues[ i ] && jQuery.isFunction( resolveValues[ i ].promise ) ) {
127                                         resolveValues[ i ].promise()
128                                                 .done( updateFunc( i, resolveContexts, resolveValues ) )
129                                                 .fail( deferred.reject )
130                                                 .progress( updateFunc( i, progressContexts, progressValues ) );
131                                 } else {
132                                         --remaining;
133                                 }
134                         }
135                 }
137                 // if we're not waiting on anything, resolve the master
138                 if ( !remaining ) {
139                         deferred.resolveWith( resolveContexts, resolveValues );
140                 }
142                 return deferred.promise();
143         }