]> git.pond.sub.org Git - eow/blob - static/dojo-release-1.1.1/dojo/_base/Deferred.js
add Dojo 1.1.1
[eow] / static / dojo-release-1.1.1 / dojo / _base / Deferred.js
1 if(!dojo._hasResource["dojo._base.Deferred"]){ //_hasResource checks added by build. Do not use _hasResource directly in your code.
2 dojo._hasResource["dojo._base.Deferred"] = true;
3 dojo.provide("dojo._base.Deferred");
4 dojo.require("dojo._base.lang");
5
6 dojo.Deferred = function(/*Function?*/ canceller){
7         // summary:
8         //              Encapsulates a sequence of callbacks in response to a value that
9         //              may not yet be available.  This is modeled after the Deferred class
10         //              from Twisted <http://twistedmatrix.com>.
11         // description:
12         //              JavaScript has no threads, and even if it did, threads are hard.
13         //              Deferreds are a way of abstracting non-blocking events, such as the
14         //              final response to an XMLHttpRequest. Deferreds create a promise to
15         //              return a response a some point in the future and an easy way to
16         //              register your interest in receiving that response.
17         //
18         //              The most important methods for Deffered users are:
19         //
20         //                      * addCallback(handler)
21         //                      * addErrback(handler)
22         //                      * callback(result)
23         //                      * errback(result)
24         //
25         //              In general, when a function returns a Deferred, users then "fill
26         //              in" the second half of the contract by registering callbacks and
27         //              error handlers. You may register as many callback and errback
28         //              handlers as you like and they will be executed in the order
29         //              registered when a result is provided. Usually this result is
30         //              provided as the result of an asynchronous operation. The code
31         //              "managing" the Deferred (the code that made the promise to provide
32         //              an answer later) will use the callback() and errback() methods to
33         //              communicate with registered listeners about the result of the
34         //              operation. At this time, all registered result handlers are called
35         //              *with the most recent result value*.
36         //
37         //              Deferred callback handlers are treated as a chain, and each item in
38         //              the chain is required to return a value that will be fed into
39         //              successive handlers. The most minimal callback may be registered
40         //              like this:
41         //
42         //              |       var d = new dojo.Deferred();
43         //              |       d.addCallback(function(result){ return result; });
44         //
45         //              Perhaps the most common mistake when first using Deferreds is to
46         //              forget to return a value (in most cases, the value you were
47         //              passed).
48         //
49         //              The sequence of callbacks is internally represented as a list of
50         //              2-tuples containing the callback/errback pair.  For example, the
51         //              following call sequence:
52         //              
53         //              |       var d = new dojo.Deferred();
54         //              |       d.addCallback(myCallback);
55         //              |       d.addErrback(myErrback);
56         //              |       d.addBoth(myBoth);
57         //              |       d.addCallbacks(myCallback, myErrback);
58         //
59         //              is translated into a Deferred with the following internal
60         //              representation:
61         //
62         //              |       [
63         //              |               [myCallback, null],
64         //              |               [null, myErrback],
65         //              |               [myBoth, myBoth],
66         //              |               [myCallback, myErrback]
67         //              |       ]
68         //
69         //              The Deferred also keeps track of its current status (fired).  Its
70         //              status may be one of three things:
71         //
72         //                      * -1: no value yet (initial condition)
73         //                      * 0: success
74         //                      * 1: error
75         //      
76         //              A Deferred will be in the error state if one of the following three
77         //              conditions are met:
78         //
79         //                      1. The result given to callback or errback is "instanceof" Error
80         //                      2. The previous callback or errback raised an exception while
81         //                         executing
82         //                      3. The previous callback or errback returned a value
83         //                         "instanceof" Error
84         //
85         //              Otherwise, the Deferred will be in the success state. The state of
86         //              the Deferred determines the next element in the callback sequence
87         //              to run.
88         //
89         //              When a callback or errback occurs with the example deferred chain,
90         //              something equivalent to the following will happen (imagine
91         //              that exceptions are caught and returned):
92         //
93         //              |       // d.callback(result) or d.errback(result)
94         //              |       if(!(result instanceof Error)){
95         //              |               result = myCallback(result);
96         //              |       }
97         //              |       if(result instanceof Error){
98         //              |               result = myErrback(result);
99         //              |       }
100         //              |       result = myBoth(result);
101         //              |       if(result instanceof Error){
102         //              |               result = myErrback(result);
103         //              |       }else{
104         //              |               result = myCallback(result);
105         //              |       }
106         //
107         //              The result is then stored away in case another step is added to the
108         //              callback sequence.      Since the Deferred already has a value
109         //              available, any new callbacks added will be called immediately.
110         //
111         //              There are two other "advanced" details about this implementation
112         //              that are useful:
113         //
114         //              Callbacks are allowed to return Deferred instances themselves, so
115         //              you can build complicated sequences of events with ease.
116         //
117         //              The creator of the Deferred may specify a canceller.  The canceller
118         //              is a function that will be called if Deferred.cancel is called
119         //              before the Deferred fires. You can use this to implement clean
120         //              aborting of an XMLHttpRequest, etc. Note that cancel will fire the
121         //              deferred with a CancelledError (unless your canceller returns
122         //              another kind of error), so the errbacks should be prepared to
123         //              handle that error for cancellable Deferreds.
124         // example:
125         //      |       var deferred = new dojo.Deferred();
126         //      |       setTimeout(function(){ deferred.callback({success: true}); }, 1000);
127         //      |       return deferred;
128         // example:
129         //              Deferred objects are often used when making code asynchronous. It
130         //              may be easiest to write functions in a synchronous manner and then
131         //              split code using a deferred to trigger a response to a long-lived
132         //              operation. For example, instead of register a callback function to
133         //              denote when a rendering operation completes, the function can
134         //              simply return a deferred:
135         //
136         //              |       // callback style:
137         //              |       function renderLotsOfData(data, callback){
138         //              |               var success = false
139         //              |               try{
140         //              |                       for(var x in data){
141         //              |                               renderDataitem(data[x]);
142         //              |                       }
143         //              |                       success = true;
144         //              |               }catch(e){ }
145         //              |               if(callback){
146         //              |                       callback(success);
147         //              |               }
148         //              |       }
149         //
150         //              |       // using callback style
151         //              |       renderLotsOfData(someDataObj, function(success){
152         //              |               // handles success or failure
153         //              |               if(!success){
154         //              |                       promptUserToRecover();
155         //              |               }
156         //              |       });
157         //              |       // NOTE: no way to add another callback here!!
158         // example:
159         //              Using a Deferred doesn't simplify the sending code any, but it
160         //              provides a standard interface for callers and senders alike,
161         //              providing both with a simple way to service multiple callbacks for
162         //              an operation and freeing both sides from worrying about details
163         //              such as "did this get called already?". With Deferreds, new
164         //              callbacks can be added at any time.
165         //
166         //              |       // Deferred style:
167         //              |       function renderLotsOfData(data){
168         //              |               var d = new dojo.Deferred();
169         //              |               try{
170         //              |                       for(var x in data){
171         //              |                               renderDataitem(data[x]);
172         //              |                       }
173         //              |                       d.callback(true);
174         //              |               }catch(e){ 
175         //              |                       d.errback(new Error("rendering failed"));
176         //              |               }
177         //              |               return d;
178         //              |       }
179         //
180         //              |       // using Deferred style
181         //              |       renderLotsOfData(someDataObj).addErrback(function(){
182         //              |               promptUserToRecover();
183         //              |       });
184         //              |       // NOTE: addErrback and addCallback both return the Deferred
185         //              |       // again, so we could chain adding callbacks or save the
186         //              |       // deferred for later should we need to be notified again.
187         // example:
188         //              In this example, renderLotsOfData is syncrhonous and so both
189         //              versions are pretty artificial. Putting the data display on a
190         //              timeout helps show why Deferreds rock:
191         //
192         //              |       // Deferred style and async func
193         //              |       function renderLotsOfData(data){
194         //              |               var d = new dojo.Deferred();
195         //              |               setTimeout(function(){
196         //              |                       try{
197         //              |                               for(var x in data){
198         //              |                                       renderDataitem(data[x]);
199         //              |                               }
200         //              |                               d.callback(true);
201         //              |                       }catch(e){ 
202         //              |                               d.errback(new Error("rendering failed"));
203         //              |                       }
204         //              |               }, 100);
205         //              |               return d;
206         //              |       }
207         //
208         //              |       // using Deferred style
209         //              |       renderLotsOfData(someDataObj).addErrback(function(){
210         //              |               promptUserToRecover();
211         //              |       });
212         //
213         //              Note that the caller doesn't have to change his code at all to
214         //              handle the asynchronous case.
215
216         this.chain = [];
217         this.id = this._nextId();
218         this.fired = -1;
219         this.paused = 0;
220         this.results = [null, null];
221         this.canceller = canceller;
222         this.silentlyCancelled = false;
223 };
224
225 dojo.extend(dojo.Deferred, {
226         /*
227         makeCalled: function(){
228                 // summary:
229                 //              returns a new, empty deferred, which is already in the called
230                 //              state. Calling callback() or errback() on this deferred will
231                 //              yeild an error and adding new handlers to it will result in
232                 //              them being called immediately.
233                 var deferred = new dojo.Deferred();
234                 deferred.callback();
235                 return deferred;
236         },
237
238         toString: function(){
239                 var state;
240                 if(this.fired == -1){
241                         state = 'unfired';
242                 }else{
243                         state = this.fired ? 'success' : 'error';
244                 }
245                 return 'Deferred(' + this.id + ', ' + state + ')';
246         },
247         */
248
249         _nextId: (function(){
250                 var n = 1;
251                 return function(){ return n++; };
252         })(),
253
254         cancel: function(){
255                 // summary:     
256                 //              Cancels a Deferred that has not yet received a value, or is
257                 //              waiting on another Deferred as its value.
258                 // description:
259                 //              If a canceller is defined, the canceller is called. If the
260                 //              canceller did not return an error, or there was no canceller,
261                 //              then the errback chain is started.
262                 var err;
263                 if(this.fired == -1){
264                         if(this.canceller){
265                                 err = this.canceller(this);
266                         }else{
267                                 this.silentlyCancelled = true;
268                         }
269                         if(this.fired == -1){
270                                 if(!(err instanceof Error)){
271                                         var res = err;
272                                         err = new Error("Deferred Cancelled");
273                                         err.dojoType = "cancel";
274                                         err.cancelResult = res;
275                                 }
276                                 this.errback(err);
277                         }
278                 }else if(       (this.fired == 0) &&
279                                         (this.results[0] instanceof dojo.Deferred)
280                 ){
281                         this.results[0].cancel();
282                 }
283         },
284                         
285
286         _resback: function(res){
287                 // summary:
288                 //              The private primitive that means either callback or errback
289                 this.fired = ((res instanceof Error) ? 1 : 0);
290                 this.results[this.fired] = res;
291                 this._fire();
292         },
293
294         _check: function(){
295                 if(this.fired != -1){
296                         if(!this.silentlyCancelled){
297                                 throw new Error("already called!");
298                         }
299                         this.silentlyCancelled = false;
300                         return;
301                 }
302         },
303
304         callback: function(res){
305                 //      summary:        
306                 //              Begin the callback sequence with a non-error value.
307                 
308                 /*
309                 callback or errback should only be called once on a given
310                 Deferred.
311                 */
312                 this._check();
313                 this._resback(res);
314         },
315
316         errback: function(/*Error*/res){
317                 //      summary: 
318                 //              Begin the callback sequence with an error result.
319                 this._check();
320                 if(!(res instanceof Error)){
321                         res = new Error(res);
322                 }
323                 this._resback(res);
324         },
325
326         addBoth: function(/*Function|Object*/cb, /*String?*/cbfn){
327                 //      summary:
328                 //              Add the same function as both a callback and an errback as the
329                 //              next element on the callback sequence.This is useful for code
330                 //              that you want to guarantee to run, e.g. a finalizer.
331                 var enclosed = dojo.hitch.apply(dojo, arguments);
332                 return this.addCallbacks(enclosed, enclosed);
333         },
334
335         addCallback: function(/*Function|Object*/cb, /*String?*/cbfn /*...*/){
336                 //      summary: 
337                 //              Add a single callback to the end of the callback sequence.
338                 return this.addCallbacks(dojo.hitch.apply(dojo, arguments));
339         },
340
341         addErrback: function(cb, cbfn){
342                 //      summary: 
343                 //              Add a single callback to the end of the callback sequence.
344                 return this.addCallbacks(null, dojo.hitch.apply(dojo, arguments));
345         },
346
347         addCallbacks: function(cb, eb){
348                 // summary: 
349                 //              Add separate callback and errback to the end of the callback
350                 //              sequence.
351                 this.chain.push([cb, eb])
352                 if(this.fired >= 0){
353                         this._fire();
354                 }
355                 return this;
356         },
357
358         _fire: function(){
359                 // summary: 
360                 //              Used internally to exhaust the callback sequence when a result
361                 //              is available.
362                 var chain = this.chain;
363                 var fired = this.fired;
364                 var res = this.results[fired];
365                 var self = this;
366                 var cb = null;
367                 while(
368                         (chain.length > 0) &&
369                         (this.paused == 0)
370                 ){
371                         // Array
372                         var f = chain.shift()[fired];
373                         if(!f){ continue; }
374                         try{
375                                 res = f(res);
376                                 fired = ((res instanceof Error) ? 1 : 0);
377                                 if(res instanceof dojo.Deferred){
378                                         cb = function(res){
379                                                 self._resback(res);
380                                                 // inlined from _pause()
381                                                 self.paused--;
382                                                 if(
383                                                         (self.paused == 0) && 
384                                                         (self.fired >= 0)
385                                                 ){
386                                                         self._fire();
387                                                 }
388                                         }
389                                         // inlined from _unpause
390                                         this.paused++;
391                                 }
392                         }catch(err){
393                                 console.debug(err);
394                                 fired = 1;
395                                 res = err;
396                         }
397                 }
398                 this.fired = fired;
399                 this.results[fired] = res;
400                 if((cb)&&(this.paused)){
401                         // this is for "tail recursion" in case the dependent
402                         // deferred is already fired
403                         res.addBoth(cb);
404                 }
405         }
406 });
407
408 }