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