source: Dev/branches/rest-dojo-ui/client/dojo/_base/Deferred.js @ 263

Last change on this file since 263 was 256, checked in by hendrikvanantwerpen, 13 years ago

Reworked project structure based on REST interaction and Dojo library. As
soon as this is stable, the old jQueryUI branch can be removed (it's
kept for reference).

File size: 13.2 KB
Line 
1define(["./kernel", "./lang"], function(dojo, lang){
2        // module:
3        //              dojo/_base/Deferred
4        // summary:
5        //              This module defines dojo.Deferred.
6
7        var mutator = function(){};
8        var freeze = Object.freeze || function(){};
9        // A deferred provides an API for creating and resolving a promise.
10        dojo.Deferred = function(/*Function?*/ canceller){
11                // summary:
12                //              Deferreds provide a generic means for encapsulating an asynchronous
13                //              operation and notifying users of the completion and result of the operation.
14                // description:
15                //              The dojo.Deferred API is based on the concept of promises that provide a
16                //              generic interface into the eventual completion of an asynchronous action.
17                //              The motivation for promises fundamentally is about creating a
18                //              separation of concerns that allows one to achieve the same type of
19                //              call patterns and logical data flow in asynchronous code as can be
20                //              achieved in synchronous code. Promises allows one
21                //              to be able to call a function purely with arguments needed for
22                //              execution, without conflating the call with concerns of whether it is
23                //              sync or async. One shouldn't need to alter a call's arguments if the
24                //              implementation switches from sync to async (or vice versa). By having
25                //              async functions return promises, the concerns of making the call are
26                //              separated from the concerns of asynchronous interaction (which are
27                //              handled by the promise).
28                //
29                //              The dojo.Deferred is a type of promise that provides methods for fulfilling the
30                //              promise with a successful result or an error. The most important method for
31                //              working with Dojo's promises is the then() method, which follows the
32                //              CommonJS proposed promise API. An example of using a Dojo promise:
33                //
34                //              |       var resultingPromise = someAsyncOperation.then(function(result){
35                //              |               ... handle result ...
36                //              |       },
37                //              |       function(error){
38                //              |               ... handle error ...
39                //              |       });
40                //
41                //              The .then() call returns a new promise that represents the result of the
42                //              execution of the callback. The callbacks will never affect the original promises value.
43                //
44                //              The dojo.Deferred instances also provide the following functions for backwards compatibility:
45                //
46                //                      * addCallback(handler)
47                //                      * addErrback(handler)
48                //                      * callback(result)
49                //                      * errback(result)
50                //
51                //              Callbacks are allowed to return promises themselves, so
52                //              you can build complicated sequences of events with ease.
53                //
54                //              The creator of the Deferred may specify a canceller.  The canceller
55                //              is a function that will be called if Deferred.cancel is called
56                //              before the Deferred fires. You can use this to implement clean
57                //              aborting of an XMLHttpRequest, etc. Note that cancel will fire the
58                //              deferred with a CancelledError (unless your canceller returns
59                //              another kind of error), so the errbacks should be prepared to
60                //              handle that error for cancellable Deferreds.
61                // example:
62                //      |       var deferred = new dojo.Deferred();
63                //      |       setTimeout(function(){ deferred.callback({success: true}); }, 1000);
64                //      |       return deferred;
65                // example:
66                //              Deferred objects are often used when making code asynchronous. It
67                //              may be easiest to write functions in a synchronous manner and then
68                //              split code using a deferred to trigger a response to a long-lived
69                //              operation. For example, instead of register a callback function to
70                //              denote when a rendering operation completes, the function can
71                //              simply return a deferred:
72                //
73                //              |       // callback style:
74                //              |       function renderLotsOfData(data, callback){
75                //              |               var success = false
76                //              |               try{
77                //              |                       for(var x in data){
78                //              |                               renderDataitem(data[x]);
79                //              |                       }
80                //              |                       success = true;
81                //              |               }catch(e){ }
82                //              |               if(callback){
83                //              |                       callback(success);
84                //              |               }
85                //              |       }
86                //
87                //              |       // using callback style
88                //              |       renderLotsOfData(someDataObj, function(success){
89                //              |               // handles success or failure
90                //              |               if(!success){
91                //              |                       promptUserToRecover();
92                //              |               }
93                //              |       });
94                //              |       // NOTE: no way to add another callback here!!
95                // example:
96                //              Using a Deferred doesn't simplify the sending code any, but it
97                //              provides a standard interface for callers and senders alike,
98                //              providing both with a simple way to service multiple callbacks for
99                //              an operation and freeing both sides from worrying about details
100                //              such as "did this get called already?". With Deferreds, new
101                //              callbacks can be added at any time.
102                //
103                //              |       // Deferred style:
104                //              |       function renderLotsOfData(data){
105                //              |               var d = new dojo.Deferred();
106                //              |               try{
107                //              |                       for(var x in data){
108                //              |                               renderDataitem(data[x]);
109                //              |                       }
110                //              |                       d.callback(true);
111                //              |               }catch(e){
112                //              |                       d.errback(new Error("rendering failed"));
113                //              |               }
114                //              |               return d;
115                //              |       }
116                //
117                //              |       // using Deferred style
118                //              |       renderLotsOfData(someDataObj).then(null, function(){
119                //              |               promptUserToRecover();
120                //              |       });
121                //              |       // NOTE: addErrback and addCallback both return the Deferred
122                //              |       // again, so we could chain adding callbacks or save the
123                //              |       // deferred for later should we need to be notified again.
124                // example:
125                //              In this example, renderLotsOfData is synchronous and so both
126                //              versions are pretty artificial. Putting the data display on a
127                //              timeout helps show why Deferreds rock:
128                //
129                //              |       // Deferred style and async func
130                //              |       function renderLotsOfData(data){
131                //              |               var d = new dojo.Deferred();
132                //              |               setTimeout(function(){
133                //              |                       try{
134                //              |                               for(var x in data){
135                //              |                                       renderDataitem(data[x]);
136                //              |                               }
137                //              |                               d.callback(true);
138                //              |                       }catch(e){
139                //              |                               d.errback(new Error("rendering failed"));
140                //              |                       }
141                //              |               }, 100);
142                //              |               return d;
143                //              |       }
144                //
145                //              |       // using Deferred style
146                //              |       renderLotsOfData(someDataObj).then(null, function(){
147                //              |               promptUserToRecover();
148                //              |       });
149                //
150                //              Note that the caller doesn't have to change his code at all to
151                //              handle the asynchronous case.
152
153                var result, finished, isError, head, nextListener;
154                var promise = (this.promise = {});
155
156                function complete(value){
157                        if(finished){
158                                throw new Error("This deferred has already been resolved");
159                        }
160                        result = value;
161                        finished = true;
162                        notify();
163                }
164                function notify(){
165                        var mutated;
166                        while(!mutated && nextListener){
167                                var listener = nextListener;
168                                nextListener = nextListener.next;
169                                if((mutated = (listener.progress == mutator))){ // assignment and check
170                                        finished = false;
171                                }
172                                var func = (isError ? listener.error : listener.resolved);
173                                if(func){
174                                        try{
175                                                var newResult = func(result);
176                                                if (newResult && typeof newResult.then === "function"){
177                                                        newResult.then(lang.hitch(listener.deferred, "resolve"), lang.hitch(listener.deferred, "reject"), lang.hitch(listener.deferred, "progress"));
178                                                        continue;
179                                                }
180                                                var unchanged = mutated && newResult === undefined;
181                                                if(mutated && !unchanged){
182                                                        isError = newResult instanceof Error;
183                                                }
184                                                listener.deferred[unchanged && isError ? "reject" : "resolve"](unchanged ? result : newResult);
185                                        }catch(e){
186                                                listener.deferred.reject(e);
187                                        }
188                                }else{
189                                        if(isError){
190                                                listener.deferred.reject(result);
191                                        }else{
192                                                listener.deferred.resolve(result);
193                                        }
194                                }
195                        }
196                }
197                // calling resolve will resolve the promise
198                this.resolve = this.callback = function(value){
199                        // summary:
200                        //              Fulfills the Deferred instance successfully with the provide value
201                        this.fired = 0;
202                        this.results = [value, null];
203                        complete(value);
204                };
205
206
207                // calling error will indicate that the promise failed
208                this.reject = this.errback = function(error){
209                        // summary:
210                        //              Fulfills the Deferred instance as an error with the provided error
211                        isError = true;
212                        this.fired = 1;
213                        complete(error);
214                        this.results = [null, error];
215                        if(!error || error.log !== false){
216                                (dojo.config.deferredOnError || function(x){ console.error(x); })(error);
217                        }
218                };
219                // call progress to provide updates on the progress on the completion of the promise
220                this.progress = function(update){
221                        // summary:
222                        //              Send progress events to all listeners
223                        var listener = nextListener;
224                        while(listener){
225                                var progress = listener.progress;
226                                progress && progress(update);
227                                listener = listener.next;
228                        }
229                };
230                this.addCallbacks = function(callback, errback){
231                        // summary:
232                        //              Adds callback and error callback for this deferred instance.
233                        // callback: Function?
234                        //              The callback attached to this deferred object.
235                        // errback: Function?
236                        //              The error callback attached to this deferred object.
237                        // returns:
238                        //              Returns this deferred object.
239                        this.then(callback, errback, mutator);
240                        return this;    // dojo.Deferred
241                };
242                // provide the implementation of the promise
243                promise.then = this.then = function(/*Function?*/resolvedCallback, /*Function?*/errorCallback, /*Function?*/progressCallback){
244                        // summary:
245                        //              Adds a fulfilledHandler, errorHandler, and progressHandler to be called for
246                        //              completion of a promise. The fulfilledHandler is called when the promise
247                        //              is fulfilled. The errorHandler is called when a promise fails. The
248                        //              progressHandler is called for progress events. All arguments are optional
249                        //              and non-function values are ignored. The progressHandler is not only an
250                        //              optional argument, but progress events are purely optional. Promise
251                        //              providers are not required to ever create progress events.
252                        //
253                        //              This function will return a new promise that is fulfilled when the given
254                        //              fulfilledHandler or errorHandler callback is finished. This allows promise
255                        //              operations to be chained together. The value returned from the callback
256                        //              handler is the fulfillment value for the returned promise. If the callback
257                        //              throws an error, the returned promise will be moved to failed state.
258                        //
259                        // returns:
260                        //              Returns a new promise that represents the result of the
261                        //              execution of the callback. The callbacks will never affect the original promises value.
262                        // example:
263                        //              An example of using a CommonJS compliant promise:
264                        //              |       asyncComputeTheAnswerToEverything().
265                        //              |               then(addTwo).
266                        //              |               then(printResult, onError);
267                        //              |       >44
268                        //
269                        var returnDeferred = progressCallback == mutator ? this : new dojo.Deferred(promise.cancel);
270                        var listener = {
271                                resolved: resolvedCallback,
272                                error: errorCallback,
273                                progress: progressCallback,
274                                deferred: returnDeferred
275                        };
276                        if(nextListener){
277                                head = head.next = listener;
278                        }
279                        else{
280                                nextListener = head = listener;
281                        }
282                        if(finished){
283                                notify();
284                        }
285                        return returnDeferred.promise; // Promise
286                };
287                var deferred = this;
288                promise.cancel = this.cancel = function (){
289                        // summary:
290                        //              Cancels the asynchronous operation
291                        if(!finished){
292                                var error = canceller && canceller(deferred);
293                                if(!finished){
294                                        if (!(error instanceof Error)){
295                                                error = new Error(error);
296                                        }
297                                        error.log = false;
298                                        deferred.reject(error);
299                                }
300                        }
301                };
302                freeze(promise);
303        };
304        lang.extend(dojo.Deferred, {
305                addCallback: function (/*Function*/ callback){
306                        // summary:
307                        //              Adds successful callback for this deferred instance.
308                        // returns:
309                        //              Returns this deferred object.
310                        return this.addCallbacks(lang.hitch.apply(dojo, arguments));    // dojo.Deferred
311                },
312
313                addErrback: function (/*Function*/ errback){
314                        // summary:
315                        //              Adds error callback for this deferred instance.
316                        // returns:
317                        //              Returns this deferred object.
318                        return this.addCallbacks(null, lang.hitch.apply(dojo, arguments));      // dojo.Deferred
319                },
320
321                addBoth: function (/*Function*/ callback){
322                        // summary:
323                        //              Add handler as both successful callback and error callback for this deferred instance.
324                        // returns:
325                        //              Returns this deferred object.
326                        var enclosed = lang.hitch.apply(dojo, arguments);
327                        return this.addCallbacks(enclosed, enclosed);   // dojo.Deferred
328                },
329                fired: -1
330        });
331
332        dojo.Deferred.when = dojo.when = function(promiseOrValue, /*Function?*/ callback, /*Function?*/ errback, /*Function?*/ progressHandler){
333                // summary:
334                //              This provides normalization between normal synchronous values and
335                //              asynchronous promises, so you can interact with them in a common way
336                // returns:
337                //              Returns a new promise that represents the result of the execution of callback
338                //              when parameter "promiseOrValue" is promise.
339                //              Returns the execution result of callback when parameter "promiseOrValue" is value.
340                // example:
341                //              |       function printFirstAndLast(items){
342                //              |               dojo.when(findFirst(items), console.log);
343                //              |               dojo.when(findLast(items), console.log);
344                //              |       }
345                //              |       function findFirst(items){
346                //              |               return dojo.when(items, function(items){
347                //              |                       return items[0];
348                //              |               });
349                //              |       }
350                //              |       function findLast(items){
351                //              |               return dojo.when(items, function(items){
352                //              |                       return items[items.length - 1];
353                //              |               });
354                //              |       }
355                //              And now all three of his functions can be used sync or async.
356                //              |       printFirstAndLast([1,2,3,4]) will work just as well as
357                //              |       printFirstAndLast(dojo.xhrGet(...));
358
359                if(promiseOrValue && typeof promiseOrValue.then === "function"){
360                        return promiseOrValue.then(callback, errback, progressHandler);
361                }
362                return callback ? callback(promiseOrValue) : promiseOrValue;    // Promise
363        };
364
365        return dojo.Deferred;
366});
Note: See TracBrowser for help on using the repository browser.