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