Subversion Repositories Applications.papyrus

Rev

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);
                }
        }
});

}