source: [view]
define("dojo/_base/Deferred", ["dojo/lib/kernel", "dojo/_base/lang"], function(dojo){
(function(){
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(dojo.hitch(listener.deferred, "resolve"), dojo.hitch(listener.deferred, "reject"));
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;
}