1 | define(["./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 | }); |
---|