[483] | 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 | }); |
---|