]> git.wh0rd.org - tt-rss.git/blame - lib/dojo/_base/Deferred.js.uncompressed.js
make precache_headlines_idle() start slower
[tt-rss.git] / lib / dojo / _base / Deferred.js.uncompressed.js
CommitLineData
1354d172
AD
1define("dojo/_base/Deferred", ["./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});