]>
Commit | Line | Data |
---|---|---|
f0cfe83e AD |
1 | define("dojo/Deferred", [ |
2 | "./has", | |
3 | "./_base/lang", | |
4 | "./errors/CancelError", | |
5 | "./promise/Promise", | |
6 | "./promise/instrumentation" | |
7 | ], function(has, lang, CancelError, Promise, instrumentation){ | |
8 | "use strict"; | |
9 | ||
10 | // module: | |
11 | // dojo/Deferred | |
12 | ||
13 | var PROGRESS = 0, | |
14 | RESOLVED = 1, | |
15 | REJECTED = 2; | |
16 | var FULFILLED_ERROR_MESSAGE = "This deferred has already been fulfilled."; | |
17 | ||
18 | var freezeObject = Object.freeze || function(){}; | |
19 | ||
20 | var signalWaiting = function(waiting, type, result, rejection, deferred){ | |
21 | if( 1 ){ | |
22 | if(type === REJECTED && Deferred.instrumentRejected && waiting.length === 0){ | |
23 | Deferred.instrumentRejected(result, false, rejection, deferred); | |
24 | } | |
25 | } | |
26 | ||
27 | for(var i = 0; i < waiting.length; i++){ | |
28 | signalListener(waiting[i], type, result, rejection); | |
29 | } | |
30 | }; | |
31 | ||
32 | var signalListener = function(listener, type, result, rejection){ | |
33 | var func = listener[type]; | |
34 | var deferred = listener.deferred; | |
35 | if(func){ | |
36 | try{ | |
37 | var newResult = func(result); | |
38 | if(type === PROGRESS){ | |
39 | if(typeof newResult !== "undefined"){ | |
40 | signalDeferred(deferred, type, newResult); | |
41 | } | |
42 | }else{ | |
43 | if(newResult && typeof newResult.then === "function"){ | |
44 | listener.cancel = newResult.cancel; | |
45 | newResult.then( | |
46 | // Only make resolvers if they're actually going to be used | |
47 | makeDeferredSignaler(deferred, RESOLVED), | |
48 | makeDeferredSignaler(deferred, REJECTED), | |
49 | makeDeferredSignaler(deferred, PROGRESS)); | |
50 | return; | |
51 | } | |
52 | signalDeferred(deferred, RESOLVED, newResult); | |
53 | } | |
54 | }catch(error){ | |
55 | signalDeferred(deferred, REJECTED, error); | |
56 | } | |
57 | }else{ | |
58 | signalDeferred(deferred, type, result); | |
59 | } | |
60 | ||
61 | if( 1 ){ | |
62 | if(type === REJECTED && Deferred.instrumentRejected){ | |
63 | Deferred.instrumentRejected(result, !!func, rejection, deferred.promise); | |
64 | } | |
65 | } | |
66 | }; | |
67 | ||
68 | var makeDeferredSignaler = function(deferred, type){ | |
69 | return function(value){ | |
70 | signalDeferred(deferred, type, value); | |
71 | }; | |
72 | }; | |
73 | ||
74 | var signalDeferred = function(deferred, type, result){ | |
75 | if(!deferred.isCanceled()){ | |
76 | switch(type){ | |
77 | case PROGRESS: | |
78 | deferred.progress(result); | |
79 | break; | |
80 | case RESOLVED: | |
81 | deferred.resolve(result); | |
82 | break; | |
83 | case REJECTED: | |
84 | deferred.reject(result); | |
85 | break; | |
86 | } | |
87 | } | |
88 | }; | |
89 | ||
90 | var Deferred = function(canceler){ | |
91 | // summary: | |
92 | // Creates a new deferred. This API is preferred over | |
93 | // `dojo/_base/Deferred`. | |
94 | // description: | |
95 | // Creates a new deferred, as an abstraction over (primarily) | |
96 | // asynchronous operations. The deferred is the private interface | |
97 | // that should not be returned to calling code. That's what the | |
98 | // `promise` is for. See `dojo/promise/Promise`. | |
99 | // canceler: Function? | |
100 | // Will be invoked if the deferred is canceled. The canceler | |
101 | // receives the reason the deferred was canceled as its argument. | |
102 | // The deferred is rejected with its return value, or a new | |
103 | // `dojo/errors/CancelError` instance. | |
104 | ||
105 | // promise: dojo/promise/Promise | |
106 | // The public promise object that clients can add callbacks to. | |
107 | var promise = this.promise = new Promise(); | |
108 | ||
109 | var deferred = this; | |
110 | var fulfilled, result, rejection; | |
111 | var canceled = false; | |
112 | var waiting = []; | |
113 | ||
114 | if( 1 && Error.captureStackTrace){ | |
115 | Error.captureStackTrace(deferred, Deferred); | |
116 | Error.captureStackTrace(promise, Deferred); | |
117 | } | |
118 | ||
119 | this.isResolved = promise.isResolved = function(){ | |
120 | // summary: | |
121 | // Checks whether the deferred has been resolved. | |
122 | // returns: Boolean | |
123 | ||
124 | return fulfilled === RESOLVED; | |
125 | }; | |
126 | ||
127 | this.isRejected = promise.isRejected = function(){ | |
128 | // summary: | |
129 | // Checks whether the deferred has been rejected. | |
130 | // returns: Boolean | |
131 | ||
132 | return fulfilled === REJECTED; | |
133 | }; | |
134 | ||
135 | this.isFulfilled = promise.isFulfilled = function(){ | |
136 | // summary: | |
137 | // Checks whether the deferred has been resolved or rejected. | |
138 | // returns: Boolean | |
139 | ||
140 | return !!fulfilled; | |
141 | }; | |
142 | ||
143 | this.isCanceled = promise.isCanceled = function(){ | |
144 | // summary: | |
145 | // Checks whether the deferred has been canceled. | |
146 | // returns: Boolean | |
147 | ||
148 | return canceled; | |
149 | }; | |
150 | ||
151 | this.progress = function(update, strict){ | |
152 | // summary: | |
153 | // Emit a progress update on the deferred. | |
154 | // description: | |
155 | // Emit a progress update on the deferred. Progress updates | |
156 | // can be used to communicate updates about the asynchronous | |
157 | // operation before it has finished. | |
158 | // update: any | |
159 | // The progress update. Passed to progbacks. | |
160 | // strict: Boolean? | |
161 | // If strict, will throw an error if the deferred has already | |
162 | // been fulfilled and consequently no progress can be emitted. | |
163 | // returns: dojo/promise/Promise | |
164 | // Returns the original promise for the deferred. | |
165 | ||
166 | if(!fulfilled){ | |
167 | signalWaiting(waiting, PROGRESS, update, null, deferred); | |
168 | return promise; | |
169 | }else if(strict === true){ | |
170 | throw new Error(FULFILLED_ERROR_MESSAGE); | |
171 | }else{ | |
172 | return promise; | |
173 | } | |
174 | }; | |
175 | ||
176 | this.resolve = function(value, strict){ | |
177 | // summary: | |
178 | // Resolve the deferred. | |
179 | // description: | |
180 | // Resolve the deferred, putting it in a success state. | |
181 | // value: any | |
182 | // The result of the deferred. Passed to callbacks. | |
183 | // strict: Boolean? | |
184 | // If strict, will throw an error if the deferred has already | |
185 | // been fulfilled and consequently cannot be resolved. | |
186 | // returns: dojo/promise/Promise | |
187 | // Returns the original promise for the deferred. | |
188 | ||
189 | if(!fulfilled){ | |
190 | // Set fulfilled, store value. After signaling waiting listeners unset | |
191 | // waiting. | |
192 | signalWaiting(waiting, fulfilled = RESOLVED, result = value, null, deferred); | |
193 | waiting = null; | |
194 | return promise; | |
195 | }else if(strict === true){ | |
196 | throw new Error(FULFILLED_ERROR_MESSAGE); | |
197 | }else{ | |
198 | return promise; | |
199 | } | |
200 | }; | |
201 | ||
202 | var reject = this.reject = function(error, strict){ | |
203 | // summary: | |
204 | // Reject the deferred. | |
205 | // description: | |
206 | // Reject the deferred, putting it in an error state. | |
207 | // error: any | |
208 | // The error result of the deferred. Passed to errbacks. | |
209 | // strict: Boolean? | |
210 | // If strict, will throw an error if the deferred has already | |
211 | // been fulfilled and consequently cannot be rejected. | |
212 | // returns: dojo/promise/Promise | |
213 | // Returns the original promise for the deferred. | |
214 | ||
215 | if(!fulfilled){ | |
216 | if( 1 && Error.captureStackTrace){ | |
217 | Error.captureStackTrace(rejection = {}, reject); | |
218 | } | |
219 | signalWaiting(waiting, fulfilled = REJECTED, result = error, rejection, deferred); | |
220 | waiting = null; | |
221 | return promise; | |
222 | }else if(strict === true){ | |
223 | throw new Error(FULFILLED_ERROR_MESSAGE); | |
224 | }else{ | |
225 | return promise; | |
226 | } | |
227 | }; | |
228 | ||
229 | this.then = promise.then = function(callback, errback, progback){ | |
230 | // summary: | |
231 | // Add new callbacks to the deferred. | |
232 | // description: | |
233 | // Add new callbacks to the deferred. Callbacks can be added | |
234 | // before or after the deferred is fulfilled. | |
235 | // callback: Function? | |
236 | // Callback to be invoked when the promise is resolved. | |
237 | // Receives the resolution value. | |
238 | // errback: Function? | |
239 | // Callback to be invoked when the promise is rejected. | |
240 | // Receives the rejection error. | |
241 | // progback: Function? | |
242 | // Callback to be invoked when the promise emits a progress | |
243 | // update. Receives the progress update. | |
244 | // returns: dojo/promise/Promise | |
245 | // Returns a new promise for the result of the callback(s). | |
246 | // This can be used for chaining many asynchronous operations. | |
247 | ||
248 | var listener = [progback, callback, errback]; | |
249 | // Ensure we cancel the promise we're waiting for, or if callback/errback | |
250 | // have returned a promise, cancel that one. | |
251 | listener.cancel = promise.cancel; | |
252 | listener.deferred = new Deferred(function(reason){ | |
253 | // Check whether cancel is really available, returned promises are not | |
254 | // required to expose `cancel` | |
255 | return listener.cancel && listener.cancel(reason); | |
256 | }); | |
257 | if(fulfilled && !waiting){ | |
258 | signalListener(listener, fulfilled, result, rejection); | |
259 | }else{ | |
260 | waiting.push(listener); | |
261 | } | |
262 | return listener.deferred.promise; | |
263 | }; | |
264 | ||
265 | this.cancel = promise.cancel = function(reason, strict){ | |
266 | // summary: | |
267 | // Inform the deferred it may cancel its asynchronous operation. | |
268 | // description: | |
269 | // Inform the deferred it may cancel its asynchronous operation. | |
270 | // The deferred's (optional) canceler is invoked and the | |
271 | // deferred will be left in a rejected state. Can affect other | |
272 | // promises that originate with the same deferred. | |
273 | // reason: any | |
274 | // A message that may be sent to the deferred's canceler, | |
275 | // explaining why it's being canceled. | |
276 | // strict: Boolean? | |
277 | // If strict, will throw an error if the deferred has already | |
278 | // been fulfilled and consequently cannot be canceled. | |
279 | // returns: any | |
280 | // Returns the rejection reason if the deferred was canceled | |
281 | // normally. | |
282 | ||
283 | if(!fulfilled){ | |
284 | // Cancel can be called even after the deferred is fulfilled | |
285 | if(canceler){ | |
286 | var returnedReason = canceler(reason); | |
287 | reason = typeof returnedReason === "undefined" ? reason : returnedReason; | |
288 | } | |
289 | canceled = true; | |
290 | if(!fulfilled){ | |
291 | // Allow canceler to provide its own reason, but fall back to a CancelError | |
292 | if(typeof reason === "undefined"){ | |
293 | reason = new CancelError(); | |
294 | } | |
295 | reject(reason); | |
296 | return reason; | |
297 | }else if(fulfilled === REJECTED && result === reason){ | |
298 | return reason; | |
299 | } | |
300 | }else if(strict === true){ | |
301 | throw new Error(FULFILLED_ERROR_MESSAGE); | |
302 | } | |
303 | }; | |
304 | ||
305 | freezeObject(promise); | |
306 | }; | |
307 | ||
308 | Deferred.prototype.toString = function(){ | |
309 | // returns: String | |
310 | // Returns `[object Deferred]`. | |
311 | ||
312 | return "[object Deferred]"; | |
313 | }; | |
314 | ||
315 | if(instrumentation){ | |
316 | instrumentation(Deferred); | |
317 | } | |
318 | ||
319 | return Deferred; | |
320 | }); |