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