define("dojo/_base/Deferred", ["./kernel", "./lang"], function(dojo, lang){ // module: // dojo/_base/Deferred // summary: // This module defines dojo.Deferred. var mutator = function(){}; var freeze = Object.freeze || function(){}; // A deferred provides an API for creating and resolving a promise. dojo.Deferred = function(/*Function?*/ canceller){ // summary: // Deferreds provide a generic means for encapsulating an asynchronous // operation and notifying users of the completion and result of the operation. // description: // The dojo.Deferred API is based on the concept of promises that provide a // generic interface into the eventual completion of an asynchronous action. // The motivation for promises fundamentally is about creating a // separation of concerns that allows one to achieve the same type of // call patterns and logical data flow in asynchronous code as can be // achieved in synchronous code. Promises allows one // to be able to call a function purely with arguments needed for // execution, without conflating the call with concerns of whether it is // sync or async. One shouldn't need to alter a call's arguments if the // implementation switches from sync to async (or vice versa). By having // async functions return promises, the concerns of making the call are // separated from the concerns of asynchronous interaction (which are // handled by the promise). // // The dojo.Deferred is a type of promise that provides methods for fulfilling the // promise with a successful result or an error. The most important method for // working with Dojo's promises is the then() method, which follows the // CommonJS proposed promise API. An example of using a Dojo promise: // // | var resultingPromise = someAsyncOperation.then(function(result){ // | ... handle result ... // | }, // | function(error){ // | ... handle error ... // | }); // // The .then() call returns a new promise that represents the result of the // execution of the callback. The callbacks will never affect the original promises value. // // The dojo.Deferred instances also provide the following functions for backwards compatibility: // // * addCallback(handler) // * addErrback(handler) // * callback(result) // * errback(result) // // Callbacks are allowed to return promises themselves, so // you can build complicated sequences of events with ease. // // The creator of the Deferred may specify a canceller. The canceller // is a function that will be called if Deferred.cancel is called // before the Deferred fires. You can use this to implement clean // aborting of an XMLHttpRequest, etc. Note that cancel will fire the // deferred with a CancelledError (unless your canceller returns // another kind of error), so the errbacks should be prepared to // handle that error for cancellable Deferreds. // example: // | var deferred = new dojo.Deferred(); // | setTimeout(function(){ deferred.callback({success: true}); }, 1000); // | return deferred; // example: // Deferred objects are often used when making code asynchronous. It // may be easiest to write functions in a synchronous manner and then // split code using a deferred to trigger a response to a long-lived // operation. For example, instead of register a callback function to // denote when a rendering operation completes, the function can // simply return a deferred: // // | // callback style: // | function renderLotsOfData(data, callback){ // | var success = false // | try{ // | for(var x in data){ // | renderDataitem(data[x]); // | } // | success = true; // | }catch(e){ } // | if(callback){ // | callback(success); // | } // | } // // | // using callback style // | renderLotsOfData(someDataObj, function(success){ // | // handles success or failure // | if(!success){ // | promptUserToRecover(); // | } // | }); // | // NOTE: no way to add another callback here!! // example: // Using a Deferred doesn't simplify the sending code any, but it // provides a standard interface for callers and senders alike, // providing both with a simple way to service multiple callbacks for // an operation and freeing both sides from worrying about details // such as "did this get called already?". With Deferreds, new // callbacks can be added at any time. // // | // Deferred style: // | function renderLotsOfData(data){ // | var d = new dojo.Deferred(); // | try{ // | for(var x in data){ // | renderDataitem(data[x]); // | } // | d.callback(true); // | }catch(e){ // | d.errback(new Error("rendering failed")); // | } // | return d; // | } // // | // using Deferred style // | renderLotsOfData(someDataObj).then(null, function(){ // | promptUserToRecover(); // | }); // | // NOTE: addErrback and addCallback both return the Deferred // | // again, so we could chain adding callbacks or save the // | // deferred for later should we need to be notified again. // example: // In this example, renderLotsOfData is synchronous and so both // versions are pretty artificial. Putting the data display on a // timeout helps show why Deferreds rock: // // | // Deferred style and async func // | function renderLotsOfData(data){ // | var d = new dojo.Deferred(); // | setTimeout(function(){ // | try{ // | for(var x in data){ // | renderDataitem(data[x]); // | } // | d.callback(true); // | }catch(e){ // | d.errback(new Error("rendering failed")); // | } // | }, 100); // | return d; // | } // // | // using Deferred style // | renderLotsOfData(someDataObj).then(null, function(){ // | promptUserToRecover(); // | }); // // Note that the caller doesn't have to change his code at all to // handle the asynchronous case. var result, finished, isError, head, nextListener; var promise = (this.promise = {}); function complete(value){ if(finished){ throw new Error("This deferred has already been resolved"); } result = value; finished = true; notify(); } function notify(){ var mutated; while(!mutated && nextListener){ var listener = nextListener; nextListener = nextListener.next; if((mutated = (listener.progress == mutator))){ // assignment and check finished = false; } var func = (isError ? listener.error : listener.resolved); if(func){ try{ var newResult = func(result); if (newResult && typeof newResult.then === "function"){ newResult.then(lang.hitch(listener.deferred, "resolve"), lang.hitch(listener.deferred, "reject"), lang.hitch(listener.deferred, "progress")); continue; } var unchanged = mutated && newResult === undefined; if(mutated && !unchanged){ isError = newResult instanceof Error; } listener.deferred[unchanged && isError ? "reject" : "resolve"](unchanged ? result : newResult); }catch(e){ listener.deferred.reject(e); } }else{ if(isError){ listener.deferred.reject(result); }else{ listener.deferred.resolve(result); } } } } // calling resolve will resolve the promise this.resolve = this.callback = function(value){ // summary: // Fulfills the Deferred instance successfully with the provide value this.fired = 0; this.results = [value, null]; complete(value); }; // calling error will indicate that the promise failed this.reject = this.errback = function(error){ // summary: // Fulfills the Deferred instance as an error with the provided error isError = true; this.fired = 1; complete(error); this.results = [null, error]; if(!error || error.log !== false){ (dojo.config.deferredOnError || function(x){ console.error(x); })(error); } }; // call progress to provide updates on the progress on the completion of the promise this.progress = function(update){ // summary: // Send progress events to all listeners var listener = nextListener; while(listener){ var progress = listener.progress; progress && progress(update); listener = listener.next; } }; this.addCallbacks = function(callback, errback){ // summary: // Adds callback and error callback for this deferred instance. // callback: Function? // The callback attached to this deferred object. // errback: Function? // The error callback attached to this deferred object. // returns: // Returns this deferred object. this.then(callback, errback, mutator); return this; // dojo.Deferred }; // provide the implementation of the promise promise.then = this.then = function(/*Function?*/resolvedCallback, /*Function?*/errorCallback, /*Function?*/progressCallback){ // summary: // Adds a fulfilledHandler, errorHandler, and progressHandler to be called for // completion of a promise. The fulfilledHandler is called when the promise // is fulfilled. The errorHandler is called when a promise fails. The // progressHandler is called for progress events. All arguments are optional // and non-function values are ignored. The progressHandler is not only an // optional argument, but progress events are purely optional. Promise // providers are not required to ever create progress events. // // This function will return a new promise that is fulfilled when the given // fulfilledHandler or errorHandler callback is finished. This allows promise // operations to be chained together. The value returned from the callback // handler is the fulfillment value for the returned promise. If the callback // throws an error, the returned promise will be moved to failed state. // // returns: // Returns a new promise that represents the result of the // execution of the callback. The callbacks will never affect the original promises value. // example: // An example of using a CommonJS compliant promise: // | asyncComputeTheAnswerToEverything(). // | then(addTwo). // | then(printResult, onError); // | >44 // var returnDeferred = progressCallback == mutator ? this : new dojo.Deferred(promise.cancel); var listener = { resolved: resolvedCallback, error: errorCallback, progress: progressCallback, deferred: returnDeferred }; if(nextListener){ head = head.next = listener; } else{ nextListener = head = listener; } if(finished){ notify(); } return returnDeferred.promise; // Promise }; var deferred = this; promise.cancel = this.cancel = function (){ // summary: // Cancels the asynchronous operation if(!finished){ var error = canceller && canceller(deferred); if(!finished){ if (!(error instanceof Error)){ error = new Error(error); } error.log = false; deferred.reject(error); } } }; freeze(promise); }; lang.extend(dojo.Deferred, { addCallback: function (/*Function*/ callback){ // summary: // Adds successful callback for this deferred instance. // returns: // Returns this deferred object. return this.addCallbacks(lang.hitch.apply(dojo, arguments)); // dojo.Deferred }, addErrback: function (/*Function*/ errback){ // summary: // Adds error callback for this deferred instance. // returns: // Returns this deferred object. return this.addCallbacks(null, lang.hitch.apply(dojo, arguments)); // dojo.Deferred }, addBoth: function (/*Function*/ callback){ // summary: // Add handler as both successful callback and error callback for this deferred instance. // returns: // Returns this deferred object. var enclosed = lang.hitch.apply(dojo, arguments); return this.addCallbacks(enclosed, enclosed); // dojo.Deferred }, fired: -1 }); dojo.Deferred.when = dojo.when = function(promiseOrValue, /*Function?*/ callback, /*Function?*/ errback, /*Function?*/ progressHandler){ // summary: // This provides normalization between normal synchronous values and // asynchronous promises, so you can interact with them in a common way // returns: // Returns a new promise that represents the result of the execution of callback // when parameter "promiseOrValue" is promise. // Returns the execution result of callback when parameter "promiseOrValue" is value. // example: // | function printFirstAndLast(items){ // | dojo.when(findFirst(items), console.log); // | dojo.when(findLast(items), console.log); // | } // | function findFirst(items){ // | return dojo.when(items, function(items){ // | return items[0]; // | }); // | } // | function findLast(items){ // | return dojo.when(items, function(items){ // | return items[items.length - 1]; // | }); // | } // And now all three of his functions can be used sync or async. // | printFirstAndLast([1,2,3,4]) will work just as well as // | printFirstAndLast(dojo.xhrGet(...)); if(promiseOrValue && typeof promiseOrValue.then === "function"){ return promiseOrValue.then(callback, errback, progressHandler); } return callback ? callback(promiseOrValue) : promiseOrValue; // Promise }; return dojo.Deferred; });