Blame | Last modification | View Log | RSS feed
if(!dojo._hasResource["dojo._base.Deferred"]){ //_hasResource checks added by build. Do not use _hasResource directly in your code.
dojo._hasResource["dojo._base.Deferred"] = true;
dojo.provide("dojo._base.Deferred");
dojo.require("dojo._base.lang");
dojo.Deferred = function(/*Function?*/ canceller){
// summary:
// Encapsulates a sequence of callbacks in response to a value that
// may not yet be available. This is modeled after the Deferred class
// from Twisted <http://twistedmatrix.com>.
// description:
// JavaScript has no threads, and even if it did, threads are hard.
// Deferreds are a way of abstracting non-blocking events, such as the
// final response to an XMLHttpRequest. Deferreds create a promise to
// return a response a some point in the future and an easy way to
// register your interest in receiving that response.
//
// The most important methods for Deffered users are:
//
// * addCallback(handler)
// * addErrback(handler)
// * callback(result)
// * errback(result)
//
// In general, when a function returns a Deferred, users then "fill
// in" the second half of the contract by registering callbacks and
// error handlers. You may register as many callback and errback
// handlers as you like and they will be executed in the order
// registered when a result is provided. Usually this result is
// provided as the result of an asynchronous operation. The code
// "managing" the Deferred (the code that made the promise to provide
// an answer later) will use the callback() and errback() methods to
// communicate with registered listeners about the result of the
// operation. At this time, all registered result handlers are called
// *with the most recent result value*.
//
// Deferred callback handlers are treated as a chain, and each item in
// the chain is required to return a value that will be fed into
// successive handlers. The most minimal callback may be registered
// like this:
//
// | var d = new dojo.Deferred();
// | d.addCallback(function(result){ return result; });
//
// Perhaps the most common mistake when first using Deferreds is to
// forget to return a value (in most cases, the value you were
// passed).
//
// The sequence of callbacks is internally represented as a list of
// 2-tuples containing the callback/errback pair. For example, the
// following call sequence:
//
// | var d = new dojo.Deferred();
// | d.addCallback(myCallback);
// | d.addErrback(myErrback);
// | d.addBoth(myBoth);
// | d.addCallbacks(myCallback, myErrback);
//
// is translated into a Deferred with the following internal
// representation:
//
// | [
// | [myCallback, null],
// | [null, myErrback],
// | [myBoth, myBoth],
// | [myCallback, myErrback]
// | ]
//
// The Deferred also keeps track of its current status (fired). Its
// status may be one of three things:
//
// * -1: no value yet (initial condition)
// * 0: success
// * 1: error
//
// A Deferred will be in the error state if one of the following three
// conditions are met:
//
// 1. The result given to callback or errback is "instanceof" Error
// 2. The previous callback or errback raised an exception while
// executing
// 3. The previous callback or errback returned a value
// "instanceof" Error
//
// Otherwise, the Deferred will be in the success state. The state of
// the Deferred determines the next element in the callback sequence
// to run.
//
// When a callback or errback occurs with the example deferred chain,
// something equivalent to the following will happen (imagine
// that exceptions are caught and returned):
//
// | // d.callback(result) or d.errback(result)
// | if(!(result instanceof Error)){
// | result = myCallback(result);
// | }
// | if(result instanceof Error){
// | result = myErrback(result);
// | }
// | result = myBoth(result);
// | if(result instanceof Error){
// | result = myErrback(result);
// | }else{
// | result = myCallback(result);
// | }
//
// The result is then stored away in case another step is added to the
// callback sequence. Since the Deferred already has a value
// available, any new callbacks added will be called immediately.
//
// There are two other "advanced" details about this implementation
// that are useful:
//
// Callbacks are allowed to return Deferred instances 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).addErrback(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 syncrhonous 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).addErrback(function(){
// | promptUserToRecover();
// | });
//
// Note that the caller doesn't have to change his code at all to
// handle the asynchronous case.
this.chain = [];
this.id = this._nextId();
this.fired = -1;
this.paused = 0;
this.results = [null, null];
this.canceller = canceller;
this.silentlyCancelled = false;
};
dojo.extend(dojo.Deferred, {
/*
makeCalled: function(){
// summary:
// returns a new, empty deferred, which is already in the called
// state. Calling callback() or errback() on this deferred will
// yeild an error and adding new handlers to it will result in
// them being called immediately.
var deferred = new dojo.Deferred();
deferred.callback();
return deferred;
},
toString: function(){
var state;
if(this.fired == -1){
state = 'unfired';
}else{
state = this.fired ? 'success' : 'error';
}
return 'Deferred(' + this.id + ', ' + state + ')';
},
*/
_nextId: (function(){
var n = 1;
return function(){ return n++; };
})(),
cancel: function(){
// summary:
// Cancels a Deferred that has not yet received a value, or is
// waiting on another Deferred as its value.
// description:
// If a canceller is defined, the canceller is called. If the
// canceller did not return an error, or there was no canceller,
// then the errback chain is started.
var err;
if(this.fired == -1){
if(this.canceller){
err = this.canceller(this);
}else{
this.silentlyCancelled = true;
}
if(this.fired == -1){
if(!(err instanceof Error)){
var res = err;
err = new Error("Deferred Cancelled");
err.dojoType = "cancel";
err.cancelResult = res;
}
this.errback(err);
}
}else if( (this.fired == 0) &&
(this.results[0] instanceof dojo.Deferred)
){
this.results[0].cancel();
}
},
_resback: function(res){
// summary:
// The private primitive that means either callback or errback
this.fired = ((res instanceof Error) ? 1 : 0);
this.results[this.fired] = res;
this._fire();
},
_check: function(){
if(this.fired != -1){
if(!this.silentlyCancelled){
throw new Error("already called!");
}
this.silentlyCancelled = false;
return;
}
},
callback: function(res){
// summary: Begin the callback sequence with a non-error value.
/*
callback or errback should only be called once on a given
Deferred.
*/
this._check();
this._resback(res);
},
errback: function(/*Error*/res){
// summary:
// Begin the callback sequence with an error result.
this._check();
if(!(res instanceof Error)){
res = new Error(res);
}
this._resback(res);
},
addBoth: function(/*Function||Object*/cb, /*Optional, String*/cbfn){
// summary:
// Add the same function as both a callback and an errback as the
// next element on the callback sequence. This is useful for code
// that you want to guarantee to run, e.g. a finalizer.
var enclosed = dojo.hitch(cb, cbfn);
if(arguments.length > 2){
enclosed = dojo.partial(enclosed, arguments, 2);
}
return this.addCallbacks(enclosed, enclosed);
},
addCallback: function(cb, cbfn){
// summary:
// Add a single callback to the end of the callback sequence.
var enclosed = dojo.hitch(cb, cbfn);
if(arguments.length > 2){
enclosed = dojo.partial(enclosed, arguments, 2);
}
return this.addCallbacks(enclosed, null);
},
addErrback: function(cb, cbfn){
// summary:
// Add a single callback to the end of the callback sequence.
var enclosed = dojo.hitch(cb, cbfn);
if(arguments.length > 2){
enclosed = dojo.partial(enclosed, arguments, 2);
}
return this.addCallbacks(null, enclosed);
},
addCallbacks: function(cb, eb){
// summary:
// Add separate callback and errback to the end of the callback
// sequence.
this.chain.push([cb, eb])
if(this.fired >= 0){
this._fire();
}
return this;
},
_fire: function(){
// summary:
// Used internally to exhaust the callback sequence when a result
// is available.
var chain = this.chain;
var fired = this.fired;
var res = this.results[fired];
var self = this;
var cb = null;
while(
(chain.length > 0) &&
(this.paused == 0)
){
// Array
var f = chain.shift()[fired];
if(!f){ continue; }
try{
res = f(res);
fired = ((res instanceof Error) ? 1 : 0);
if(res instanceof dojo.Deferred){
cb = function(res){
self._resback(res);
// inlined from _pause()
self.paused--;
if(
(self.paused == 0) &&
(self.fired >= 0)
){
self._fire();
}
}
// inlined from _unpause
this.paused++;
}
}catch(err){
console.debug(err);
fired = 1;
res = err;
}
}
this.fired = fired;
this.results[fired] = res;
if((cb)&&(this.paused)){
// this is for "tail recursion" in case the dependent
// deferred is already fired
res.addBoth(cb);
}
}
});
}