731 lines
20 KiB
JavaScript
Executable File
731 lines
20 KiB
JavaScript
Executable File
(function(define){
|
|
define(function(require,exports){
|
|
|
|
// Kris Zyp
|
|
|
|
// this is based on the CommonJS spec for promises:
|
|
// http://wiki.commonjs.org/wiki/Promises
|
|
// Includes convenience functions for promises, much of this is taken from Tyler Close's ref_send
|
|
// and Kris Kowal's work on promises.
|
|
// // MIT License
|
|
|
|
// A typical usage:
|
|
// A default Promise constructor can be used to create a self-resolving deferred/promise:
|
|
// var Promise = require("promise").Promise;
|
|
// var promise = new Promise();
|
|
// asyncOperation(function(){
|
|
// Promise.resolve("succesful result");
|
|
// });
|
|
// promise -> given to the consumer
|
|
//
|
|
// A consumer can use the promise
|
|
// promise.then(function(result){
|
|
// ... when the action is complete this is executed ...
|
|
// },
|
|
// function(error){
|
|
// ... executed when the promise fails
|
|
// });
|
|
//
|
|
// Alternately, a provider can create a deferred and resolve it when it completes an action.
|
|
// The deferred object a promise object that provides a separation of consumer and producer to protect
|
|
// promises from being fulfilled by untrusted code.
|
|
// var defer = require("promise").defer;
|
|
// var deferred = defer();
|
|
// asyncOperation(function(){
|
|
// deferred.resolve("succesful result");
|
|
// });
|
|
// deferred.promise -> given to the consumer
|
|
//
|
|
// Another way that a consumer can use the promise (using promise.then is also allowed)
|
|
// var when = require("promise").when;
|
|
// when(promise,function(result){
|
|
// ... when the action is complete this is executed ...
|
|
// },
|
|
// function(error){
|
|
// ... executed when the promise fails
|
|
// });
|
|
|
|
exports.errorTimeout = 100;
|
|
var freeze = Object.freeze || function(){};
|
|
|
|
/**
|
|
* Default constructor that creates a self-resolving Promise. Not all promise implementations
|
|
* need to use this constructor.
|
|
*/
|
|
var Promise = function(canceller){
|
|
};
|
|
|
|
/**
|
|
* Promise implementations must provide a "then" function.
|
|
*/
|
|
Promise.prototype.then = function(resolvedCallback, errorCallback, progressCallback){
|
|
throw new TypeError("The Promise base class is abstract, this function must be implemented by the Promise implementation");
|
|
};
|
|
|
|
/**
|
|
* If an implementation of a promise supports a concurrency model that allows
|
|
* execution to block until the promise is resolved, the wait function may be
|
|
* added.
|
|
*/
|
|
/**
|
|
* If an implementation of a promise can be cancelled, it may add this function
|
|
*/
|
|
// Promise.prototype.cancel = function(){
|
|
// };
|
|
|
|
Promise.prototype.get = function(propertyName){
|
|
return this.then(function(value){
|
|
return value[propertyName];
|
|
});
|
|
};
|
|
|
|
Promise.prototype.put = function(propertyName, value){
|
|
return this.then(function(object){
|
|
return object[propertyName] = value;
|
|
});
|
|
};
|
|
|
|
Promise.prototype.call = function(functionName /*, args */){
|
|
var fnArgs = Array.prototype.slice.call(arguments, 1);
|
|
return this.then(function(value){
|
|
return value[functionName].apply(value, fnArgs);
|
|
});
|
|
};
|
|
|
|
/**
|
|
* This can be used to conviently resolve a promise with auto-handling of errors:
|
|
* setTimeout(deferred.resolverCallback(function(){
|
|
* return doSomething();
|
|
* }), 100);
|
|
*/
|
|
Promise.prototype.resolverCallback = function(callback){
|
|
var self = this;
|
|
return function(){
|
|
try{
|
|
self.resolve(callback());
|
|
}catch(e){
|
|
self.reject(e);
|
|
}
|
|
}
|
|
};
|
|
|
|
/** Dojo/NodeJS methods*/
|
|
Promise.prototype.addCallback = function(callback){
|
|
return this.then(callback);
|
|
};
|
|
|
|
Promise.prototype.addErrback = function(errback){
|
|
return this.then(function(){}, errback);
|
|
};
|
|
|
|
/*Dojo methods*/
|
|
Promise.prototype.addBoth = function(callback){
|
|
return this.then(callback, callback);
|
|
};
|
|
|
|
Promise.prototype.addCallbacks = function(callback, errback){
|
|
return this.then(callback, errback);
|
|
};
|
|
|
|
/*NodeJS method*/
|
|
Promise.prototype.wait = function(){
|
|
return exports.wait(this);
|
|
};
|
|
|
|
Deferred.prototype = Promise.prototype;
|
|
// A deferred provides an API for creating and resolving a promise.
|
|
exports.Promise = exports.Deferred = exports.defer = defer;
|
|
function defer(canceller){
|
|
return new Deferred(canceller);
|
|
}
|
|
|
|
|
|
// currentContext can be set to other values
|
|
// and mirrors the global. We need to go off the global in case of multiple instances
|
|
// of this module, which isn't rare with NPM's package policy.
|
|
Object.defineProperty && Object.defineProperty(exports, "currentContext", {
|
|
set: function(value){
|
|
currentContext = value;
|
|
},
|
|
get: function(){
|
|
return currentContext;
|
|
}
|
|
});
|
|
exports.currentContext = null;
|
|
|
|
|
|
function Deferred(canceller){
|
|
var result, finished, isError, waiting = [], handled;
|
|
var promise = this.promise = new Promise();
|
|
var context = exports.currentContext;
|
|
|
|
function notifyAll(value){
|
|
var previousContext = exports.currentContext;
|
|
if(finished){
|
|
throw new Error("This deferred has already been resolved");
|
|
}
|
|
try{
|
|
if(previousContext !== context){
|
|
if(previousContext && previousContext.suspend){
|
|
previousContext.suspend();
|
|
}
|
|
exports.currentContext = context;
|
|
if(context && context.resume){
|
|
context.resume();
|
|
}
|
|
}
|
|
result = value;
|
|
finished = true;
|
|
for(var i = 0; i < waiting.length; i++){
|
|
notify(waiting[i]);
|
|
}
|
|
}
|
|
finally{
|
|
if(previousContext !== context){
|
|
if(context && context.suspend){
|
|
context.suspend();
|
|
}
|
|
if(previousContext && previousContext.resume){
|
|
previousContext.resume();
|
|
}
|
|
exports.currentContext = previousContext;
|
|
}
|
|
}
|
|
}
|
|
function notify(listener){
|
|
var func = (isError ? listener.error : listener.resolved);
|
|
if(func){
|
|
handled ?
|
|
(handled.handled = true) : (handled = true);
|
|
try{
|
|
var newResult = func(result);
|
|
if(newResult && typeof newResult.then === "function"){
|
|
newResult.then(listener.deferred.resolve, listener.deferred.reject);
|
|
return;
|
|
}
|
|
listener.deferred.resolve(newResult);
|
|
}
|
|
catch(e){
|
|
listener.deferred.reject(e);
|
|
}
|
|
}
|
|
else{
|
|
if(isError){
|
|
listener.deferred.reject(result, typeof handled === "object" ? handled : (handled = {}));
|
|
}
|
|
else{
|
|
listener.deferred.resolve.call(listener.deferred, result);
|
|
}
|
|
}
|
|
}
|
|
// calling resolve will resolve the promise
|
|
this.resolve = this.callback = this.emitSuccess = function(value){
|
|
notifyAll(value);
|
|
};
|
|
|
|
// calling error will indicate that the promise failed
|
|
var reject = this.reject = this.errback = this.emitError = function(error, handledObject){
|
|
if (typeof handledObject == "object") {
|
|
if (handled) {
|
|
handledObject.handled = true;
|
|
} else {
|
|
handled = handledObject;
|
|
}
|
|
}
|
|
isError = true;
|
|
notifyAll(error);
|
|
if (!handledObject && typeof setTimeout !== "undefined") {
|
|
if (!(typeof handled == "object" ? handled.handled : handled)) {
|
|
// set the time out if it has not already been handled
|
|
setTimeout(function () {
|
|
if (!(typeof handled == "object" ? handled.handled : handled)) {
|
|
throw error;
|
|
}
|
|
}, exports.errorTimeout);
|
|
}
|
|
}
|
|
return handled;
|
|
};
|
|
|
|
// call progress to provide updates on the progress on the completion of the promise
|
|
this.progress = function(update){
|
|
for(var i = 0; i < waiting.length; i++){
|
|
var progress = waiting[i].progress;
|
|
progress && progress(update);
|
|
}
|
|
}
|
|
// provide the implementation of the promise
|
|
this.then = promise.then = function(resolvedCallback, errorCallback, progressCallback){
|
|
var returnDeferred = new Deferred(promise.cancel);
|
|
var listener = {resolved: resolvedCallback, error: errorCallback, progress: progressCallback, deferred: returnDeferred};
|
|
if(finished){
|
|
notify(listener);
|
|
}
|
|
else{
|
|
waiting.push(listener);
|
|
}
|
|
return returnDeferred.promise;
|
|
};
|
|
var timeout;
|
|
if(typeof setTimeout !== "undefined") {
|
|
this.timeout = function (ms) {
|
|
if (ms === undefined) {
|
|
return timeout;
|
|
}
|
|
timeout = ms;
|
|
setTimeout(function () {
|
|
if (!finished) {
|
|
if (promise.cancel) {
|
|
promise.cancel(new Error("timeout"));
|
|
}
|
|
else {
|
|
reject(new Error("timeout"));
|
|
}
|
|
}
|
|
}, ms);
|
|
return promise;
|
|
};
|
|
}
|
|
|
|
if(canceller){
|
|
this.cancel = promise.cancel = function(){
|
|
var error = canceller();
|
|
if(!(error instanceof Error)){
|
|
error = new Error(error);
|
|
}
|
|
reject(error);
|
|
}
|
|
}
|
|
freeze(promise);
|
|
};
|
|
|
|
function perform(value, async, sync){
|
|
try{
|
|
if(value && typeof value.then === "function"){
|
|
value = async(value);
|
|
}
|
|
else{
|
|
value = sync(value);
|
|
}
|
|
if(value && typeof value.then === "function"){
|
|
return value;
|
|
}
|
|
var deferred = new Deferred();
|
|
deferred.resolve(value);
|
|
return deferred.promise;
|
|
}catch(e){
|
|
var deferred = new Deferred();
|
|
deferred.reject(e);
|
|
return deferred.promise;
|
|
}
|
|
|
|
}
|
|
/**
|
|
* Promise manager to make it easier to consume promises
|
|
*/
|
|
|
|
function rethrow(err){ throw err; }
|
|
|
|
/**
|
|
* Registers an observer on a promise, always returning a promise
|
|
* @param value promise or value to observe
|
|
* @param resolvedCallback function to be called with the resolved value
|
|
* @param rejectCallback function to be called with the rejection reason
|
|
* @param progressCallback function to be called when progress is made
|
|
* @return promise for the return value from the invoked callback
|
|
*/
|
|
exports.whenPromise = function(value, resolvedCallback, rejectCallback, progressCallback){
|
|
var deferred = defer();
|
|
if(value && typeof value.then === "function"){
|
|
value.then(function(next){
|
|
deferred.resolve(next);
|
|
},function(error){
|
|
deferred.reject(error);
|
|
});
|
|
rejectCallback = rejectCallback || rethrow;
|
|
}else{
|
|
deferred.resolve(value);
|
|
}
|
|
return deferred.promise.then(resolvedCallback, rejectCallback, progressCallback);
|
|
};
|
|
|
|
/**
|
|
* Registers an observer on a promise.
|
|
* @param value promise or value to observe
|
|
* @param resolvedCallback function to be called with the resolved value
|
|
* @param rejectCallback function to be called with the rejection reason
|
|
* @param progressCallback function to be called when progress is made
|
|
* @return promise for the return value from the invoked callback or the value if it
|
|
* is a non-promise value
|
|
*/
|
|
exports.when = function(value, resolvedCallback, rejectCallback, progressCallback){
|
|
if(value && typeof value.then === "function"){
|
|
if(value instanceof Promise){
|
|
return value.then(resolvedCallback, rejectCallback, progressCallback);
|
|
}
|
|
else{
|
|
return exports.whenPromise(value, resolvedCallback, rejectCallback, progressCallback);
|
|
}
|
|
}
|
|
return resolvedCallback ? resolvedCallback(value) : value;
|
|
};
|
|
|
|
/**
|
|
* This is convenience function for catching synchronously and asynchronously thrown
|
|
* errors. This is used like when() except you execute the initial action in a callback:
|
|
* whenCall(function(){
|
|
* return doSomethingThatMayReturnAPromise();
|
|
* }, successHandler, errorHandler);
|
|
*/
|
|
exports.whenCall = function(initialCallback, resolvedCallback, rejectCallback, progressCallback){
|
|
try{
|
|
return exports.when(initialCallback(), resolvedCallback, rejectCallback, progressCallback);
|
|
}catch(e){
|
|
return rejectCallback(e);
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Gets the value of a property in a future turn.
|
|
* @param target promise or value for target object
|
|
* @param property name of property to get
|
|
* @return promise for the property value
|
|
*/
|
|
exports.get = function(target, property){
|
|
return perform(target, function(target){
|
|
return target.get(property);
|
|
},
|
|
function(target){
|
|
return target[property]
|
|
});
|
|
};
|
|
|
|
/**
|
|
* Invokes a method in a future turn.
|
|
* @param target promise or value for target object
|
|
* @param methodName name of method to invoke
|
|
* @param args array of invocation arguments
|
|
* @return promise for the return value
|
|
*/
|
|
exports.call = function(target, methodName, args){
|
|
return perform(target, function(target){
|
|
return target.call(methodName, args);
|
|
},
|
|
function(target){
|
|
return target[methodName].apply(target, args);
|
|
});
|
|
};
|
|
|
|
/**
|
|
* Sets the value of a property in a future turn.
|
|
* @param target promise or value for target object
|
|
* @param property name of property to set
|
|
* @param value new value of property
|
|
* @return promise for the return value
|
|
*/
|
|
exports.put = function(target, property, value){
|
|
return perform(target, function(target){
|
|
return target.put(property, value);
|
|
},
|
|
function(target){
|
|
return target[property] = value;
|
|
});
|
|
};
|
|
|
|
|
|
/**
|
|
* Waits for the given promise to finish, blocking (and executing other events)
|
|
* if necessary to wait for the promise to finish. If target is not a promise
|
|
* it will return the target immediately. If the promise results in an reject,
|
|
* that reject will be thrown.
|
|
* @param target promise or value to wait for.
|
|
* @return the value of the promise;
|
|
*/
|
|
var queue;
|
|
//try {
|
|
// queue = require("event-loop");
|
|
//}
|
|
//catch (e) {}
|
|
exports.wait = function(target){
|
|
if(!queue){
|
|
throw new Error("Can not wait, the event-queue module is not available");
|
|
}
|
|
if(target && typeof target.then === "function"){
|
|
var isFinished, isError, result;
|
|
target.then(function(value){
|
|
isFinished = true;
|
|
result = value;
|
|
},
|
|
function(error){
|
|
isFinished = true;
|
|
isError = true;
|
|
result = error;
|
|
});
|
|
while(!isFinished){
|
|
queue.processNextEvent(true);
|
|
}
|
|
if(isError){
|
|
throw result;
|
|
}
|
|
return result;
|
|
}
|
|
else{
|
|
return target;
|
|
}
|
|
};
|
|
|
|
|
|
|
|
/**
|
|
* Takes an array of promises and returns a promise that is fulfilled once all
|
|
* the promises in the array are fulfilled
|
|
* @param array The array of promises
|
|
* @return the promise that is fulfilled when all the array is fulfilled, resolved to the array of results
|
|
*/
|
|
exports.all = function(array){
|
|
var deferred = new Deferred();
|
|
if(Object.prototype.toString.call(array) !== '[object Array]'){
|
|
array = Array.prototype.slice.call(arguments);
|
|
}
|
|
var fulfilled = 0, length = array.length, rejected = false;
|
|
var results = [];
|
|
if (length === 0) deferred.resolve(results);
|
|
else {
|
|
array.forEach(function(promise, index){
|
|
exports.when(promise,
|
|
function(value){
|
|
results[index] = value;
|
|
fulfilled++;
|
|
if(fulfilled === length){
|
|
deferred.resolve(results);
|
|
}
|
|
},
|
|
function(error){
|
|
if(!rejected){
|
|
deferred.reject(error);
|
|
}
|
|
rejected = true;
|
|
});
|
|
});
|
|
}
|
|
return deferred.promise;
|
|
};
|
|
|
|
/**
|
|
* Takes a hash of promises and returns a promise that is fulfilled once all
|
|
* the promises in the hash keys are fulfilled
|
|
* @param hash The hash of promises
|
|
* @return the promise that is fulfilled when all the hash keys is fulfilled, resolved to the hash of results
|
|
*/
|
|
exports.allKeys = function(hash){
|
|
var deferred = new Deferred();
|
|
var array = Object.keys(hash);
|
|
var fulfilled = 0, length = array.length;
|
|
var results = {};
|
|
if (length === 0) deferred.resolve(results);
|
|
else {
|
|
array.forEach(function(key){
|
|
exports.when(hash[key],
|
|
function(value){
|
|
results[key] = value;
|
|
fulfilled++;
|
|
if(fulfilled === length){
|
|
deferred.resolve(results);
|
|
}
|
|
},
|
|
deferred.reject);
|
|
});
|
|
}
|
|
return deferred.promise;
|
|
};
|
|
|
|
/**
|
|
* Takes an array of promises and returns a promise that is fulfilled when the first
|
|
* promise in the array of promises is fulfilled
|
|
* @param array The array of promises
|
|
* @return a promise that is fulfilled with the value of the value of first promise to be fulfilled
|
|
*/
|
|
exports.first = function(array){
|
|
var deferred = new Deferred();
|
|
if(Object.prototype.toString.call(array) !== '[object Array]'){
|
|
array = Array.prototype.slice.call(arguments);
|
|
}
|
|
var fulfilled;
|
|
array.forEach(function(promise, index){
|
|
exports.when(promise, function(value){
|
|
if (!fulfilled) {
|
|
fulfilled = true;
|
|
deferred.resolve(value);
|
|
}
|
|
},
|
|
function(error){
|
|
if (!fulfilled) {
|
|
fulfilled = true;
|
|
deferred.resolve(error);
|
|
}
|
|
});
|
|
});
|
|
return deferred.promise;
|
|
};
|
|
|
|
/**
|
|
* Takes an array of asynchronous functions (that return promises) and
|
|
* executes them sequentially. Each funtion is called with the return value of the last function
|
|
* @param array The array of function
|
|
* @param startingValue The value to pass to the first function
|
|
* @return the value returned from the last function
|
|
*/
|
|
exports.seq = function(array, startingValue){
|
|
array = array.concat(); // make a copy
|
|
var deferred = new Deferred();
|
|
function next(value){
|
|
var nextAction = array.shift();
|
|
if(nextAction){
|
|
exports.when(nextAction(value), next, function(error){
|
|
deferred.reject(error, true);
|
|
});
|
|
}
|
|
else {
|
|
deferred.resolve(value);
|
|
}
|
|
}
|
|
next(startingValue);
|
|
return deferred.promise;
|
|
};
|
|
|
|
|
|
/**
|
|
* Delays for a given amount of time and then fulfills the returned promise.
|
|
* @param milliseconds The number of milliseconds to delay
|
|
* @return A promise that will be fulfilled after the delay
|
|
*/
|
|
if(typeof setTimeout !== "undefined") {
|
|
exports.delay = function(milliseconds) {
|
|
var deferred = new Deferred();
|
|
setTimeout(function(){
|
|
deferred.resolve();
|
|
}, milliseconds);
|
|
return deferred.promise;
|
|
};
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
* Runs a function that takes a callback, but returns a Promise instead.
|
|
* @param func node compatible async function which takes a callback as its last argument
|
|
* @return promise for the return value from the callback from the function
|
|
*/
|
|
exports.execute = function(asyncFunction){
|
|
var args = Array.prototype.slice.call(arguments, 1);
|
|
|
|
var deferred = new Deferred();
|
|
args.push(function(error, result){
|
|
if(error) {
|
|
deferred.emitError(error);
|
|
}
|
|
else {
|
|
if(arguments.length > 2){
|
|
// if there are multiple success values, we return an array
|
|
Array.prototype.shift.call(arguments, 1);
|
|
deferred.emitSuccess(arguments);
|
|
}
|
|
else{
|
|
deferred.emitSuccess(result);
|
|
}
|
|
}
|
|
});
|
|
asyncFunction.apply(this, args);
|
|
return deferred.promise;
|
|
};
|
|
|
|
function isGeneratorFunction(obj){
|
|
return obj && obj.constructor && 'GeneratorFunction' == obj.constructor.name;
|
|
}
|
|
|
|
/**
|
|
* Promise-based coroutine trampoline
|
|
* Adapted from https://github.com/deanlandolt/copromise/blob/master/copromise.js
|
|
*/
|
|
function run(coroutine){
|
|
var deferred = defer();
|
|
(function next(value, exception) {
|
|
var result;
|
|
try {
|
|
result = exception ? coroutine.throw(value) : coroutine.next(value);
|
|
}
|
|
catch (error) {
|
|
return deferred.reject(error);
|
|
}
|
|
if (result.done) return deferred.resolve(result.value);
|
|
exports.when(result.value, next, function(error) {
|
|
next(error, true);
|
|
});
|
|
})();
|
|
return deferred.promise;
|
|
};
|
|
|
|
/**
|
|
* Creates a task from a coroutine, provided as generator. The `yield` function can be provided
|
|
* a promise (or any value) to wait on, and the value will be provided when the promise resolves.
|
|
* @param coroutine generator or generator function to treat as a coroutine
|
|
* @return promise for the return value from the coroutine
|
|
*/
|
|
exports.spawn = function(coroutine){
|
|
if (isGeneratorFunction(coroutine)) {
|
|
coroutine = coroutine();
|
|
}
|
|
return run(coroutine);
|
|
}
|
|
|
|
/**
|
|
* Converts a Node async function to a promise returning function
|
|
* @param func node compatible async function which takes a callback as its last argument
|
|
* @return A function that returns a promise
|
|
*/
|
|
exports.convertNodeAsyncFunction = function(asyncFunction, callbackNotDeclared){
|
|
var arity = asyncFunction.length;
|
|
return function(){
|
|
var deferred = new Deferred();
|
|
if(callbackNotDeclared){
|
|
arity = arguments.length + 1;
|
|
}
|
|
arguments.length = arity;
|
|
arguments[arity - 1] = function(error, result){
|
|
if(error) {
|
|
deferred.emitError(error);
|
|
}
|
|
else {
|
|
if(arguments.length > 2){
|
|
// if there are multiple success values, we return an array
|
|
Array.prototype.shift.call(arguments, 1);
|
|
deferred.emitSuccess(arguments);
|
|
}
|
|
else{
|
|
deferred.emitSuccess(result);
|
|
}
|
|
}
|
|
};
|
|
asyncFunction.apply(this, arguments);
|
|
return deferred.promise;
|
|
};
|
|
};
|
|
|
|
/**
|
|
* Returns a promise. If the object is already a Promise it is returned; otherwise
|
|
* the object is wrapped in a Promise.
|
|
* @param value The value to be treated as a Promise
|
|
* @return A promise wrapping the original value
|
|
*/
|
|
exports.as = function(value){
|
|
if (value instanceof Promise) {
|
|
return value;
|
|
} else {
|
|
var ret = defer();
|
|
ret.resolve(value);
|
|
return ret;
|
|
}
|
|
};
|
|
});
|
|
})(typeof define!="undefined"?define:function(factory){factory(require,exports)});
|