]>
Commit | Line | Data |
---|---|---|
f0cfe83e AD |
1 | define("dojo/_base/Deferred", [ |
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, 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 | // calling resolve will resolve the promise | |
210 | this.resolve = this.callback = function(value){ | |
211 | // summary: | |
212 | // Fulfills the Deferred instance successfully with the provide value | |
213 | this.fired = 0; | |
214 | this.results = [value, null]; | |
215 | complete(value); | |
216 | }; | |
217 | ||
218 | ||
219 | // calling error will indicate that the promise failed | |
220 | this.reject = this.errback = function(error){ | |
221 | // summary: | |
222 | // Fulfills the Deferred instance as an error with the provided error | |
223 | isError = true; | |
224 | this.fired = 1; | |
225 | if(has("config-useDeferredInstrumentation")){ | |
226 | if(NewDeferred.instrumentRejected){ | |
227 | NewDeferred.instrumentRejected(error, !!nextListener); | |
228 | } | |
229 | } | |
230 | complete(error); | |
231 | this.results = [null, error]; | |
232 | }; | |
233 | // call progress to provide updates on the progress on the completion of the promise | |
234 | this.progress = function(update){ | |
235 | // summary: | |
236 | // Send progress events to all listeners | |
237 | var listener = nextListener; | |
238 | while(listener){ | |
239 | var progress = listener.progress; | |
240 | progress && progress(update); | |
241 | listener = listener.next; | |
242 | } | |
243 | }; | |
244 | this.addCallbacks = function(callback, errback){ | |
245 | // summary: | |
246 | // Adds callback and error callback for this deferred instance. | |
247 | // callback: Function? | |
248 | // The callback attached to this deferred object. | |
249 | // errback: Function? | |
250 | // The error callback attached to this deferred object. | |
251 | // returns: | |
252 | // Returns this deferred object. | |
253 | this.then(callback, errback, mutator); | |
254 | return this; // Deferred | |
255 | }; | |
256 | // provide the implementation of the promise | |
257 | promise.then = this.then = function(/*Function?*/resolvedCallback, /*Function?*/errorCallback, /*Function?*/progressCallback){ | |
258 | // summary: | |
259 | // Adds a fulfilledHandler, errorHandler, and progressHandler to be called for | |
260 | // completion of a promise. The fulfilledHandler is called when the promise | |
261 | // is fulfilled. The errorHandler is called when a promise fails. The | |
262 | // progressHandler is called for progress events. All arguments are optional | |
263 | // and non-function values are ignored. The progressHandler is not only an | |
264 | // optional argument, but progress events are purely optional. Promise | |
265 | // providers are not required to ever create progress events. | |
266 | // | |
267 | // This function will return a new promise that is fulfilled when the given | |
268 | // fulfilledHandler or errorHandler callback is finished. This allows promise | |
269 | // operations to be chained together. The value returned from the callback | |
270 | // handler is the fulfillment value for the returned promise. If the callback | |
271 | // throws an error, the returned promise will be moved to failed state. | |
272 | // | |
273 | // returns: | |
274 | // Returns a new promise that represents the result of the | |
275 | // execution of the callback. The callbacks will never affect the original promises value. | |
276 | // example: | |
277 | // An example of using a CommonJS compliant promise: | |
278 | // | asyncComputeTheAnswerToEverything(). | |
279 | // | then(addTwo). | |
280 | // | then(printResult, onError); | |
281 | // | >44 | |
282 | // | |
283 | var returnDeferred = progressCallback == mutator ? this : new Deferred(promise.cancel); | |
284 | var listener = { | |
285 | resolved: resolvedCallback, | |
286 | error: errorCallback, | |
287 | progress: progressCallback, | |
288 | deferred: returnDeferred | |
289 | }; | |
290 | if(nextListener){ | |
291 | head = head.next = listener; | |
292 | } | |
293 | else{ | |
294 | nextListener = head = listener; | |
295 | } | |
296 | if(finished){ | |
297 | notify(); | |
298 | } | |
299 | return returnDeferred.promise; // Promise | |
300 | }; | |
301 | var deferred = this; | |
302 | promise.cancel = this.cancel = function(){ | |
303 | // summary: | |
304 | // Cancels the asynchronous operation | |
305 | if(!finished){ | |
306 | var error = canceller && canceller(deferred); | |
307 | if(!finished){ | |
308 | if (!(error instanceof Error)){ | |
309 | error = new CancelError(error); | |
310 | } | |
311 | error.log = false; | |
312 | deferred.reject(error); | |
313 | } | |
314 | } | |
315 | }; | |
316 | freeze(promise); | |
317 | }; | |
318 | lang.extend(Deferred, { | |
319 | addCallback: function(/*Function*/ callback){ | |
320 | // summary: | |
321 | // Adds successful callback for this deferred instance. | |
322 | // returns: | |
323 | // Returns this deferred object. | |
324 | return this.addCallbacks(lang.hitch.apply(dojo, arguments)); // Deferred | |
325 | }, | |
326 | ||
327 | addErrback: function(/*Function*/ errback){ | |
328 | // summary: | |
329 | // Adds error callback for this deferred instance. | |
330 | // returns: | |
331 | // Returns this deferred object. | |
332 | return this.addCallbacks(null, lang.hitch.apply(dojo, arguments)); // Deferred | |
333 | }, | |
334 | ||
335 | addBoth: function(/*Function*/ callback){ | |
336 | // summary: | |
337 | // Add handler as both successful callback and error callback for this deferred instance. | |
338 | // returns: | |
339 | // Returns this deferred object. | |
340 | var enclosed = lang.hitch.apply(dojo, arguments); | |
341 | return this.addCallbacks(enclosed, enclosed); // Deferred | |
342 | }, | |
343 | fired: -1 | |
344 | }); | |
345 | ||
346 | Deferred.when = dojo.when = when; | |
347 | ||
348 | return Deferred; | |
349 | }); |