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