New file |
0,0 → 1,418 |
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); |
} |
} |
}); |
|
} |