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 errbackthis.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 givenDeferred.*/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)){// Arrayvar 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 _unpausethis.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 firedres.addBoth(cb);}}});}