You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
383 lines
13 KiB
383 lines
13 KiB
define([ |
|
"./kernel", |
|
"../Deferred", |
|
"../promise/Promise", |
|
"../errors/CancelError", |
|
"../has", |
|
"./lang", |
|
"../when" |
|
], function(dojo, NewDeferred, Promise, CancelError, has, lang, when){ |
|
// module: |
|
// dojo/_base/Deferred |
|
|
|
var mutator = function(){}; |
|
var freeze = Object.freeze || function(){}; |
|
// A deferred provides an API for creating and resolving a promise. |
|
var Deferred = dojo.Deferred = function(/*Function?*/ canceller){ |
|
// summary: |
|
// Deprecated. This module defines the legacy dojo/_base/Deferred API. |
|
// New code should use dojo/Deferred instead. |
|
// description: |
|
// The Deferred API is based on the concept of promises that provide a |
|
// generic interface into the eventual completion of an asynchronous action. |
|
// The motivation for promises fundamentally is about creating a |
|
// separation of concerns that allows one to achieve the same type of |
|
// call patterns and logical data flow in asynchronous code as can be |
|
// achieved in synchronous code. Promises allows one |
|
// to be able to call a function purely with arguments needed for |
|
// execution, without conflating the call with concerns of whether it is |
|
// sync or async. One shouldn't need to alter a call's arguments if the |
|
// implementation switches from sync to async (or vice versa). By having |
|
// async functions return promises, the concerns of making the call are |
|
// separated from the concerns of asynchronous interaction (which are |
|
// handled by the promise). |
|
// |
|
// The Deferred is a type of promise that provides methods for fulfilling the |
|
// promise with a successful result or an error. The most important method for |
|
// working with Dojo's promises is the then() method, which follows the |
|
// CommonJS proposed promise API. An example of using a Dojo promise: |
|
// |
|
// | var resultingPromise = someAsyncOperation.then(function(result){ |
|
// | ... handle result ... |
|
// | }, |
|
// | function(error){ |
|
// | ... handle error ... |
|
// | }); |
|
// |
|
// The .then() call returns a new promise that represents the result of the |
|
// execution of the callback. The callbacks will never affect the original promises value. |
|
// |
|
// The Deferred instances also provide the following functions for backwards compatibility: |
|
// |
|
// - addCallback(handler) |
|
// - addErrback(handler) |
|
// - callback(result) |
|
// - errback(result) |
|
// |
|
// Callbacks are allowed to return promises 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 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 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).then(null, 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 synchronous 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 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).then(null, function(){ |
|
// | promptUserToRecover(); |
|
// | }); |
|
// |
|
// Note that the caller doesn't have to change his code at all to |
|
// handle the asynchronous case. |
|
|
|
var result, finished, canceled, fired, isError, head, nextListener; |
|
var promise = (this.promise = new Promise()); |
|
|
|
function complete(value){ |
|
if(finished){ |
|
throw new Error("This deferred has already been resolved"); |
|
} |
|
result = value; |
|
finished = true; |
|
notify(); |
|
} |
|
function notify(){ |
|
var mutated; |
|
while(!mutated && nextListener){ |
|
var listener = nextListener; |
|
nextListener = nextListener.next; |
|
if((mutated = (listener.progress == mutator))){ // assignment and check |
|
finished = false; |
|
} |
|
|
|
var func = (isError ? listener.error : listener.resolved); |
|
if(has("config-useDeferredInstrumentation")){ |
|
if(isError && NewDeferred.instrumentRejected){ |
|
NewDeferred.instrumentRejected(result, !!func); |
|
} |
|
} |
|
if(func){ |
|
try{ |
|
var newResult = func(result); |
|
if (newResult && typeof newResult.then === "function"){ |
|
newResult.then(lang.hitch(listener.deferred, "resolve"), lang.hitch(listener.deferred, "reject"), lang.hitch(listener.deferred, "progress")); |
|
continue; |
|
} |
|
var unchanged = mutated && newResult === undefined; |
|
if(mutated && !unchanged){ |
|
isError = newResult instanceof Error; |
|
} |
|
listener.deferred[unchanged && isError ? "reject" : "resolve"](unchanged ? result : newResult); |
|
}catch(e){ |
|
listener.deferred.reject(e); |
|
} |
|
}else{ |
|
if(isError){ |
|
listener.deferred.reject(result); |
|
}else{ |
|
listener.deferred.resolve(result); |
|
} |
|
} |
|
} |
|
} |
|
|
|
this.isResolved = promise.isResolved = function(){ |
|
// summary: |
|
// Checks whether the deferred has been resolved. |
|
// returns: Boolean |
|
|
|
return fired == 0; |
|
}; |
|
|
|
this.isRejected = promise.isRejected = function(){ |
|
// summary: |
|
// Checks whether the deferred has been rejected. |
|
// returns: Boolean |
|
|
|
return fired == 1; |
|
}; |
|
|
|
this.isFulfilled = promise.isFulfilled = function(){ |
|
// summary: |
|
// Checks whether the deferred has been resolved or rejected. |
|
// returns: Boolean |
|
|
|
return fired >= 0; |
|
}; |
|
|
|
this.isCanceled = promise.isCanceled = function(){ |
|
// summary: |
|
// Checks whether the deferred has been canceled. |
|
// returns: Boolean |
|
|
|
return canceled; |
|
}; |
|
|
|
// calling resolve will resolve the promise |
|
this.resolve = this.callback = function(value){ |
|
// summary: |
|
// Fulfills the Deferred instance successfully with the provide value |
|
this.fired = fired = 0; |
|
this.results = [value, null]; |
|
complete(value); |
|
}; |
|
|
|
|
|
// calling error will indicate that the promise failed |
|
this.reject = this.errback = function(error){ |
|
// summary: |
|
// Fulfills the Deferred instance as an error with the provided error |
|
isError = true; |
|
this.fired = fired = 1; |
|
if(has("config-useDeferredInstrumentation")){ |
|
if(NewDeferred.instrumentRejected){ |
|
NewDeferred.instrumentRejected(error, !!nextListener); |
|
} |
|
} |
|
complete(error); |
|
this.results = [null, error]; |
|
}; |
|
// call progress to provide updates on the progress on the completion of the promise |
|
this.progress = function(update){ |
|
// summary: |
|
// Send progress events to all listeners |
|
var listener = nextListener; |
|
while(listener){ |
|
var progress = listener.progress; |
|
progress && progress(update); |
|
listener = listener.next; |
|
} |
|
}; |
|
this.addCallbacks = function(callback, errback){ |
|
// summary: |
|
// Adds callback and error callback for this deferred instance. |
|
// callback: Function? |
|
// The callback attached to this deferred object. |
|
// errback: Function? |
|
// The error callback attached to this deferred object. |
|
// returns: |
|
// Returns this deferred object. |
|
this.then(callback, errback, mutator); |
|
return this; // Deferred |
|
}; |
|
// provide the implementation of the promise |
|
promise.then = this.then = function(/*Function?*/resolvedCallback, /*Function?*/errorCallback, /*Function?*/progressCallback){ |
|
// summary: |
|
// Adds a fulfilledHandler, errorHandler, and progressHandler to be called for |
|
// completion of a promise. The fulfilledHandler is called when the promise |
|
// is fulfilled. The errorHandler is called when a promise fails. The |
|
// progressHandler is called for progress events. All arguments are optional |
|
// and non-function values are ignored. The progressHandler is not only an |
|
// optional argument, but progress events are purely optional. Promise |
|
// providers are not required to ever create progress events. |
|
// |
|
// This function will return a new promise that is fulfilled when the given |
|
// fulfilledHandler or errorHandler callback is finished. This allows promise |
|
// operations to be chained together. The value returned from the callback |
|
// handler is the fulfillment value for the returned promise. If the callback |
|
// throws an error, the returned promise will be moved to failed state. |
|
// |
|
// returns: |
|
// Returns a new promise that represents the result of the |
|
// execution of the callback. The callbacks will never affect the original promises value. |
|
// example: |
|
// An example of using a CommonJS compliant promise: |
|
// | asyncComputeTheAnswerToEverything(). |
|
// | then(addTwo). |
|
// | then(printResult, onError); |
|
// | >44 |
|
// |
|
var returnDeferred = progressCallback == mutator ? this : new Deferred(promise.cancel); |
|
var listener = { |
|
resolved: resolvedCallback, |
|
error: errorCallback, |
|
progress: progressCallback, |
|
deferred: returnDeferred |
|
}; |
|
if(nextListener){ |
|
head = head.next = listener; |
|
} |
|
else{ |
|
nextListener = head = listener; |
|
} |
|
if(finished){ |
|
notify(); |
|
} |
|
return returnDeferred.promise; // Promise |
|
}; |
|
var deferred = this; |
|
promise.cancel = this.cancel = function(){ |
|
// summary: |
|
// Cancels the asynchronous operation |
|
if(!finished){ |
|
var error = canceller && canceller(deferred); |
|
if(!finished){ |
|
if (!(error instanceof Error)){ |
|
error = new CancelError(error); |
|
} |
|
error.log = false; |
|
deferred.reject(error); |
|
} |
|
} |
|
canceled = true; |
|
}; |
|
freeze(promise); |
|
}; |
|
lang.extend(Deferred, { |
|
addCallback: function(/*Function*/ callback){ |
|
// summary: |
|
// Adds successful callback for this deferred instance. |
|
// returns: |
|
// Returns this deferred object. |
|
return this.addCallbacks(lang.hitch.apply(dojo, arguments)); // Deferred |
|
}, |
|
|
|
addErrback: function(/*Function*/ errback){ |
|
// summary: |
|
// Adds error callback for this deferred instance. |
|
// returns: |
|
// Returns this deferred object. |
|
return this.addCallbacks(null, lang.hitch.apply(dojo, arguments)); // Deferred |
|
}, |
|
|
|
addBoth: function(/*Function*/ callback){ |
|
// summary: |
|
// Add handler as both successful callback and error callback for this deferred instance. |
|
// returns: |
|
// Returns this deferred object. |
|
var enclosed = lang.hitch.apply(dojo, arguments); |
|
return this.addCallbacks(enclosed, enclosed); // Deferred |
|
}, |
|
fired: -1 |
|
}); |
|
|
|
Deferred.when = dojo.when = when; |
|
|
|
return Deferred; |
|
});
|
|
|