Plato on Github
Report Home
node_modules/when/lib/makePromise.js
Maintainability
71.99
Lines of code
842
Difficulty
130.15
Estimated Errors
5.41
Function weight
By Complexity
By SLOC
/** @license MIT License (c) copyright 2010-2014 original author or authors */ /** @author Brian Cavalier */ /** @author John Hann */ (function(define) { 'use strict'; define(function() { return function makePromise(environment) { var tasks = environment.scheduler; var objectCreate = Object.create || function(proto) { function Child() {} Child.prototype = proto; return new Child(); }; /** * Create a promise whose fate is determined by resolver * @constructor * @returns {Promise} promise * @name Promise */ function Promise(resolver, handler) { this._handler = resolver === Handler ? handler : init(resolver); } /** * Run the supplied resolver * @param resolver * @returns {makePromise.DeferredHandler} */ function init(resolver) { var handler = new DeferredHandler(); try { resolver(promiseResolve, promiseReject, promiseNotify); } catch (e) { promiseReject(e); } return handler; /** * Transition from pre-resolution state to post-resolution state, notifying * all listeners of the ultimate fulfillment or rejection * @param {*} x resolution value */ function promiseResolve (x) { handler.resolve(x); } /** * Reject this promise with reason, which will be used verbatim * @param {Error|*} reason rejection reason, strongly suggested * to be an Error type */ function promiseReject (reason) { handler.reject(reason); } /** * Issue a progress event, notifying all progress listeners * @param {*} x progress event payload to pass to all listeners */ function promiseNotify (x) { handler.notify(x); } } // Creation Promise.resolve = resolve; Promise.reject = reject; Promise.never = never; Promise._defer = defer; /** * Returns a trusted promise. If x is already a trusted promise, it is * returned, otherwise returns a new trusted Promise which follows x. * @param {*} x * @return {Promise} promise */ function resolve(x) { return isPromise(x) ? x : new Promise(Handler, new AsyncHandler(getHandler(x))); } /** * Return a reject promise with x as its reason (x is used verbatim) * @param {*} x * @returns {Promise} rejected promise */ function reject(x) { return new Promise(Handler, new AsyncHandler(new RejectedHandler(x))); } /** * Return a promise that remains pending forever * @returns {Promise} forever-pending promise. */ function never() { return foreverPendingPromise; // Should be frozen } /** * Creates an internal {promise, resolver} pair * @private * @returns {Promise} */ function defer() { return new Promise(Handler, new DeferredHandler()); } // Transformation and flow control /** * Transform this promise's fulfillment value, returning a new Promise * for the transformed result. If the promise cannot be fulfilled, onRejected * is called with the reason. onProgress *may* be called with updates toward * this promise's fulfillment. * @param {function=} onFulfilled fulfillment handler * @param {function=} onRejected rejection handler * @deprecated @param {function=} onProgress progress handler * @return {Promise} new promise */ Promise.prototype.then = function(onFulfilled, onRejected) { var parent = this._handler; if (typeof onFulfilled !== 'function' && parent.join().state() > 0) { // Short circuit: value will not change, simply share handler return new Promise(Handler, parent); } var p = this._beget(); var child = p._handler; parent.when({ resolve: child.resolve, notify: child.notify, context: child, receiver: parent.receiver, fulfilled: onFulfilled, rejected: onRejected, progress: arguments.length > 2 ? arguments[2] : void 0 }); return p; }; /** * If this promise cannot be fulfilled due to an error, call onRejected to * handle the error. Shortcut for .then(undefined, onRejected) * @param {function?} onRejected * @return {Promise} */ Promise.prototype['catch'] = function(onRejected) { return this.then(void 0, onRejected); }; /** * Private function to bind a thisArg for this promise's handlers * @private * @param {object} thisArg `this` value for all handlers attached to * the returned promise. * @returns {Promise} */ Promise.prototype._bindContext = function(thisArg) { return new Promise(Handler, new BoundHandler(this._handler, thisArg)); }; /** * Creates a new, pending promise of the same type as this promise * @private * @returns {Promise} */ Promise.prototype._beget = function() { var parent = this._handler; var child = new DeferredHandler(parent.receiver, parent.join().context); return new this.constructor(Handler, child); }; /** * Check if x is a rejected promise, and if so, delegate to handler._fatal * @private * @param {*} x */ Promise.prototype._maybeFatal = function(x) { if(!maybeThenable(x)) { return; } var handler = getHandler(x); var context = this._handler.context; handler.catchError(function() { this._fatal(context); }, handler); }; // Array combinators Promise.all = all; Promise.race = race; /** * Return a promise that will fulfill when all promises in the * input array have fulfilled, or will reject when one of the * promises rejects. * @param {array} promises array of promises * @returns {Promise} promise for array of fulfillment values */ function all(promises) { /*jshint maxcomplexity:8*/ var resolver = new DeferredHandler(); var pending = promises.length >>> 0; var results = new Array(pending); var i, h, x, s; for (i = 0; i < promises.length; ++i) { x = promises[i]; if (x === void 0 && !(i in promises)) { --pending; continue; } if (maybeThenable(x)) { h = isPromise(x) ? x._handler.join() : getHandlerUntrusted(x); s = h.state(); if (s === 0) { resolveOne(resolver, results, h, i); } else if (s > 0) { results[i] = h.value; --pending; } else { resolver.become(h); break; } } else { results[i] = x; --pending; } } if(pending === 0) { resolver.become(new FulfilledHandler(results)); } return new Promise(Handler, resolver); function resolveOne(resolver, results, handler, i) { handler.map(function(x) { results[i] = x; if(--pending === 0) { this.become(new FulfilledHandler(results)); } }, resolver); } } /** * Fulfill-reject competitive race. Return a promise that will settle * to the same state as the earliest input promise to settle. * * WARNING: The ES6 Promise spec requires that race()ing an empty array * must return a promise that is pending forever. This implementation * returns a singleton forever-pending promise, the same singleton that is * returned by Promise.never(), thus can be checked with === * * @param {array} promises array of promises to race * @returns {Promise} if input is non-empty, a promise that will settle * to the same outcome as the earliest input promise to settle. if empty * is empty, returns a promise that will never settle. */ function race(promises) { // Sigh, race([]) is untestable unless we return *something* // that is recognizable without calling .then() on it. if(Object(promises) === promises && promises.length === 0) { return never(); } var h = new DeferredHandler(); var i, x; for(i=0; i<promises.length; ++i) { x = promises[i]; if (x !== void 0 && i in promises) { getHandler(x).chain(h, h.resolve, h.reject); } } return new Promise(Handler, h); } // Promise internals /** * Get an appropriate handler for x, without checking for cycles * @private * @param {*} x * @returns {object} handler */ function getHandler(x) { if(isPromise(x)) { return x._handler.join(); } return maybeThenable(x) ? getHandlerUntrusted(x) : new FulfilledHandler(x); } function isPromise(x) { return x instanceof Promise; } /** * Get a handler for potentially untrusted thenable x * @param {*} x * @returns {object} handler */ function getHandlerUntrusted(x) { try { var untrustedThen = x.then; return typeof untrustedThen === 'function' ? new ThenableHandler(untrustedThen, x) : new FulfilledHandler(x); } catch(e) { return new RejectedHandler(e); } } /** * Handler for a promise that is pending forever * @private * @constructor */ function Handler() {} Handler.prototype.when = Handler.prototype.resolve = Handler.prototype.reject = Handler.prototype.notify = Handler.prototype._fatal = Handler.prototype._unreport = Handler.prototype._report = noop; Handler.prototype.inspect = toPendingState; Handler.prototype._state = 0; Handler.prototype.state = function() { return this._state; }; /** * Recursively collapse handler chain to find the handler * nearest to the fully resolved value. * @returns {object} handler nearest the fully resolved value */ Handler.prototype.join = function() { var h = this; while(h.handler !== void 0) { h = h.handler; } return h; }; Handler.prototype.chain = function(to, fulfilled, rejected, progress) { this.when({ resolve: noop, notify: noop, context: void 0, receiver: to, fulfilled: fulfilled, rejected: rejected, progress: progress }); }; Handler.prototype.map = function(f, to) { this.chain(to, f, to.reject, to.notify); }; Handler.prototype.catchError = function(f, to) { this.chain(to, to.resolve, f, to.notify); }; Handler.prototype.fold = function(to, f, z) { this.join().map(function(x) { getHandler(z).map(function(z) { this.resolve(tryCatchReject2(f, z, x, this.receiver)); }, this); }, to); }; /** * Handler that manages a queue of consumers waiting on a pending promise * @private * @constructor */ function DeferredHandler(receiver, inheritedContext) { Promise.createContext(this, inheritedContext); this.consumers = void 0; this.receiver = receiver; this.handler = void 0; this.resolved = false; } inherit(Handler, DeferredHandler); DeferredHandler.prototype._state = 0; DeferredHandler.prototype.inspect = function() { return this.resolved ? this.join().inspect() : toPendingState(); }; DeferredHandler.prototype.resolve = function(x) { if(!this.resolved) { this.become(getHandler(x)); } }; DeferredHandler.prototype.reject = function(x) { if(!this.resolved) { this.become(new RejectedHandler(x)); } }; DeferredHandler.prototype.join = function() { if (this.resolved) { var h = this; while(h.handler !== void 0) { h = h.handler; if(h === this) { return this.handler = new Cycle(); } } return h; } else { return this; } }; DeferredHandler.prototype.run = function() { var q = this.consumers; var handler = this.join(); this.consumers = void 0; for (var i = 0; i < q.length; ++i) { handler.when(q[i]); } }; DeferredHandler.prototype.become = function(handler) { this.resolved = true; this.handler = handler; if(this.consumers !== void 0) { tasks.enqueue(this); } if(this.context !== void 0) { handler._report(this.context); } }; DeferredHandler.prototype.when = function(continuation) { if(this.resolved) { tasks.enqueue(new ContinuationTask(continuation, this.handler)); } else { if(this.consumers === void 0) { this.consumers = [continuation]; } else { this.consumers.push(continuation); } } }; DeferredHandler.prototype.notify = function(x) { if(!this.resolved) { tasks.enqueue(new ProgressTask(this, x)); } }; DeferredHandler.prototype._report = function(context) { this.resolved && this.handler.join()._report(context); }; DeferredHandler.prototype._unreport = function() { this.resolved && this.handler.join()._unreport(); }; DeferredHandler.prototype._fatal = function(context) { var c = typeof context === 'undefined' ? this.context : context; this.resolved && this.handler.join()._fatal(c); }; /** * Abstract base for handler that delegates to another handler * @private * @param {object} handler * @constructor */ function DelegateHandler(handler) { this.handler = handler; } inherit(Handler, DelegateHandler); DelegateHandler.prototype.inspect = function() { return this.join().inspect(); }; DelegateHandler.prototype._report = function(context) { this.join()._report(context); }; DelegateHandler.prototype._unreport = function() { this.join()._unreport(); }; /** * Wrap another handler and force it into a future stack * @private * @param {object} handler * @constructor */ function AsyncHandler(handler) { DelegateHandler.call(this, handler); } inherit(DelegateHandler, AsyncHandler); AsyncHandler.prototype.when = function(continuation) { tasks.enqueue(new ContinuationTask(continuation, this.join())); }; /** * Handler that follows another handler, injecting a receiver * @private * @param {object} handler another handler to follow * @param {object=undefined} receiver * @constructor */ function BoundHandler(handler, receiver) { DelegateHandler.call(this, handler); this.receiver = receiver; } inherit(DelegateHandler, BoundHandler); BoundHandler.prototype.when = function(continuation) { // Because handlers are allowed to be shared among promises, // each of which possibly having a different receiver, we have // to insert our own receiver into the chain if it has been set // so that callbacks (f, r, u) will be called using our receiver if(this.receiver !== void 0) { continuation.receiver = this.receiver; } this.join().when(continuation); }; /** * Handler that wraps an untrusted thenable and assimilates it in a future stack * @private * @param {function} then * @param {{then: function}} thenable * @constructor */ function ThenableHandler(then, thenable) { DeferredHandler.call(this); tasks.enqueue(new AssimilateTask(then, thenable, this)); } inherit(DeferredHandler, ThenableHandler); /** * Handler for a fulfilled promise * @private * @param {*} x fulfillment value * @constructor */ function FulfilledHandler(x) { Promise.createContext(this); this.value = x; } inherit(Handler, FulfilledHandler); FulfilledHandler.prototype._state = 1; FulfilledHandler.prototype.inspect = function() { return { state: 'fulfilled', value: this.value }; }; FulfilledHandler.prototype.when = function(cont) { var x; if (typeof cont.fulfilled === 'function') { Promise.enterContext(this); x = tryCatchReject(cont.fulfilled, this.value, cont.receiver); Promise.exitContext(); } else { x = this.value; } cont.resolve.call(cont.context, x); }; var id = 0; /** * Handler for a rejected promise * @private * @param {*} x rejection reason * @constructor */ function RejectedHandler(x) { Promise.createContext(this); this.id = ++id; this.value = x; this.handled = false; this.reported = false; this._report(); } inherit(Handler, RejectedHandler); RejectedHandler.prototype._state = -1; RejectedHandler.prototype.inspect = function() { return { state: 'rejected', reason: this.value }; }; RejectedHandler.prototype.when = function(cont) { var x; if (typeof cont.rejected === 'function') { this._unreport(); Promise.enterContext(this); x = tryCatchReject(cont.rejected, this.value, cont.receiver); Promise.exitContext(); } else { x = new Promise(Handler, this); } cont.resolve.call(cont.context, x); }; RejectedHandler.prototype._report = function(context) { tasks.afterQueue(reportUnhandled, this, context); }; RejectedHandler.prototype._unreport = function() { this.handled = true; tasks.afterQueue(reportHandled, this); }; RejectedHandler.prototype._fatal = function(context) { Promise.onFatalRejection(this, context); }; function reportUnhandled(rejection, context) { if(!rejection.handled) { rejection.reported = true; Promise.onPotentiallyUnhandledRejection(rejection, context); } } function reportHandled(rejection) { if(rejection.reported) { Promise.onPotentiallyUnhandledRejectionHandled(rejection); } } // Unhandled rejection hooks // By default, everything is a noop // TODO: Better names: "annotate"? Promise.createContext = Promise.enterContext = Promise.exitContext = Promise.onPotentiallyUnhandledRejection = Promise.onPotentiallyUnhandledRejectionHandled = Promise.onFatalRejection = noop; // Errors and singletons var foreverPendingHandler = new Handler(); var foreverPendingPromise = new Promise(Handler, foreverPendingHandler); function Cycle() { RejectedHandler.call(this, new TypeError('Promise cycle')); } inherit(RejectedHandler, Cycle); // Snapshot states /** * Creates a pending state snapshot * @private * @returns {{state:'pending'}} */ function toPendingState() { return { state: 'pending' }; } // Task runners /** * Run a single consumer * @private * @constructor */ function ContinuationTask(continuation, handler) { this.continuation = continuation; this.handler = handler; } ContinuationTask.prototype.run = function() { this.handler.join().when(this.continuation); }; /** * Run a queue of progress handlers * @private * @constructor */ function ProgressTask(handler, value) { this.handler = handler; this.value = value; } ProgressTask.prototype.run = function() { var q = this.handler.consumers; if(q === void 0) { return; } // First progress handler is at index 1 for (var i = 0; i < q.length; ++i) { this._notify(q[i]); } }; ProgressTask.prototype._notify = function(continuation) { var x = typeof continuation.progress === 'function' ? tryCatchReturn(continuation.progress, this.value, continuation.receiver) : this.value; continuation.notify.call(continuation.context, x); }; /** * Assimilate a thenable, sending it's value to resolver * @private * @param {function} then * @param {object|function} thenable * @param {object} resolver * @constructor */ function AssimilateTask(then, thenable, resolver) { this._then = then; this.thenable = thenable; this.resolver = resolver; } AssimilateTask.prototype.run = function() { var h = this.resolver; tryAssimilate(this._then, this.thenable, _resolve, _reject, _notify); function _resolve(x) { h.resolve(x); } function _reject(x) { h.reject(x); } function _notify(x) { h.notify(x); } }; function tryAssimilate(then, thenable, resolve, reject, notify) { try { then.call(thenable, resolve, reject, notify); } catch (e) { reject(e); } } // Other helpers /** * @param {*} x * @returns {boolean} false iff x is guaranteed not to be a thenable */ function maybeThenable(x) { return (typeof x === 'object' || typeof x === 'function') && x !== null; } /** * Return f.call(thisArg, x), or if it throws return a rejected promise for * the thrown exception * @private */ function tryCatchReject(f, x, thisArg) { try { return f.call(thisArg, x); } catch(e) { return reject(e); } } /** * Same as above, but includes the extra argument parameter. * @private */ function tryCatchReject2(f, x, y, thisArg) { try { return f.call(thisArg, x, y); } catch(e) { return reject(e); } } /** * Return f.call(thisArg, x), or if it throws, *return* the exception * @private */ function tryCatchReturn(f, x, thisArg) { try { return f.call(thisArg, x); } catch(e) { return e; } } function inherit(Parent, Child) { Child.prototype = objectCreate(Parent.prototype); Child.prototype.constructor = Child; } function noop() {} return Promise; }; }); }(typeof define === 'function' && define.amd ? define : function(factory) { module.exports = factory(); }));