UNPKG

9.8 kBJavaScriptView Raw
1define([
2 "./has",
3 "./_base/lang",
4 "./errors/CancelError",
5 "./promise/Promise",
6 "./has!config-deferredInstrumentation?./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(has("config-deferredInstrumentation")){
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(has("config-deferredInstrumentation")){
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(has("config-deferredInstrumentation") && 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(has("config-deferredInstrumentation") && 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});