]>
Commit | Line | Data |
---|---|---|
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 |
8 | if(!dojo._hasResource["dojo._base.Deferred"]){ //_hasResource checks added by build. Do not use _hasResource directly in your code. |
9 | dojo._hasResource["dojo._base.Deferred"] = true; | |
2f01fe57 AD |
10 | dojo.provide("dojo._base.Deferred"); |
11 | dojo.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 |
313 | dojo.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 | } |