]>
Commit | Line | Data |
---|---|---|
1354d172 AD |
1 | define("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 | }); |