UNPKG

bugcore

Version:

bugcore is a JavaScript library that provides a foundational architecture for object oriented JS

722 lines (617 loc) 21.6 kB
/* * Copyright (c) 2016 airbug Inc. http://airbug.com * * bugcore may be freely distributed under the MIT license. */ //------------------------------------------------------------------------------- // Annotations //------------------------------------------------------------------------------- //@Export('Promise') //@Require('ArgUtil') //@Require('CallbackHandler') //@Require('CatchHandler') //@Require('Class') //@Require('FinallyHandler') //@Require('IIterable') //@Require('IMap') //@Require('IPromise') //@Require('List') //@Require('Obj') //@Require('Resolvers') //@Require('ThenHandler') //@Require('Throwables') //@Require('Tracer') //@Require('TypeUtil') //------------------------------------------------------------------------------- // Context //------------------------------------------------------------------------------- require('bugpack').context('*', function(bugpack) { //------------------------------------------------------------------------------- // BugPack //------------------------------------------------------------------------------- var ArgUtil = bugpack.require('ArgUtil'); var CallbackHandler = bugpack.require('CallbackHandler'); var CatchHandler = bugpack.require('CatchHandler'); var Class = bugpack.require('Class'); var FinallyHandler = bugpack.require('FinallyHandler'); var IIterable = bugpack.require('IIterable'); var IMap = bugpack.require('IMap'); var IPromise = bugpack.require('IPromise'); var List = bugpack.require('List'); var Obj = bugpack.require('Obj'); var Resolvers = bugpack.require('Resolvers'); var ThenHandler = bugpack.require('ThenHandler'); var Throwables = bugpack.require('Throwables'); var Tracer = bugpack.require('Tracer'); var TypeUtil = bugpack.require('TypeUtil'); //------------------------------------------------------------------------------- // Simplify References //------------------------------------------------------------------------------- var $error = Tracer.$error; var $trace = Tracer.$trace; //------------------------------------------------------------------------------- // Declare Class //------------------------------------------------------------------------------- /** * @class * @extends {Obj} * @implements {IPromise} */ var Promise = Class.extend(Obj, { _name: 'Promise', //------------------------------------------------------------------------------- // Constructor //------------------------------------------------------------------------------- /** * @constructs */ _constructor: function() { this._super(); //------------------------------------------------------------------------------- // Private Properties //------------------------------------------------------------------------------- /** * @private * @type {List<Handler>} */ this.handlerList = new List(); /** * @private * @type {number} */ this.processIndex = 0; /** * @private * @type {boolean} */ this.processing = false; /** * @private * @type {List<*>} */ this.reasonList = new List(); /** * @private * @type {Resolver} */ this.resolver = null; /** * @private * @type {boolean} */ this.resolving = false; /** * @private * @type {Promise.State} */ this.state = Promise.State.PENDING; /** * @private * @type {List<*>} */ this.valueList = new List(); }, //------------------------------------------------------------------------------- // Init Methods //------------------------------------------------------------------------------- /** * @param {function(function(...*), function(...*))=} promiseMethod * @return {Promise} */ init: function(promiseMethod) { var _this = this._super(); if (_this) { if (TypeUtil.isFunction(promiseMethod)) { promiseMethod.call(_this, function() { _this.resolve(ArgUtil.toArray(arguments)); }, function() { _this.reject(ArgUtil.toArray(arguments)); } ); } } return _this; }, //------------------------------------------------------------------------------- // Getters and Setters //------------------------------------------------------------------------------- /** * @return {List<Handler>} */ getHandlerList: function() { return this.handlerList; }, /** * @return {List<*>} */ getReasonList: function() { return this.reasonList; }, /** * @return {Resolver} */ getResolver: function() { return this.resolver; }, /** * @return {boolean} */ getResolving: function() { return this.resolving; }, /** * @return {Promise.State} */ getState: function() { return this.state; }, /** * @return {List<*>} */ getValueList: function() { return this.valueList; }, //------------------------------------------------------------------------------- // Convenience Methods //------------------------------------------------------------------------------- /** * @return {boolean} */ isFulfilled: function() { return this.state === Promise.State.FULFILLED; }, /** * @return {boolean} */ isPending: function() { return this.state === Promise.State.PENDING; }, /** * @return {boolean} */ isProcessing: function() { return this.processing; }, /** * @return {boolean} */ isRejected: function() { return this.state === Promise.State.REJECTED; }, /** * @return {boolean} */ isResolving: function() { return this.resolving; }, //------------------------------------------------------------------------------- // IPromise Implementation //------------------------------------------------------------------------------- /** * @param {function(Throwable, ...*=)} callback * @return {Promise} */ callback: function(callback) { var handler = this.generateCallbackHandler(callback); this.processHandlers(); return handler.getForwardPromise(); }, /** * @param {function(...*):*=} catchFunction * @return {Promise} */ catch: function(catchFunction) { var handler = this.generateCatchHandler(catchFunction); this.processHandlers(); return handler.getForwardPromise(); }, /** * @param {function():*=} finallyFunction * @return {Promise} */ finally: function(finallyFunction) { var handler = this.generateFinallyHandler(finallyFunction); this.processHandlers(); return handler.getForwardPromise(); }, /** * @param {Array<*>} reasons * @return {Promise} */ reject: function(reasons) { this.validatePromiseState(); this.doRejectPromise(reasons); return this; }, /** * @param {Array<*>} values * @return {Promise} */ resolve: function(values) { this.validatePromiseState(); this.doResolveValues(values); return this; }, /** * @param {Array<(Array<*> | IIterable<*>)>} iterables * @return {Promise} */ resolveAll: function(iterables) { this.validatePromiseState(); this.validateIterables(iterables); this.doResolveAll(iterables); return this; }, /** * @param {Array<(Object<*, *> | Map<*, *>)>} objects * @return {Promise} */ resolveProps: function(objects) { this.validatePromiseState(); this.validateObjects(objects); this.doResolveProps(objects); return this; }, /** * @param {Array<(Array<*> | IIterable<*>)>} iterables * @return {Promise} */ resolveRace: function(iterables) { this.validatePromiseState(); this.validateIterables(iterables); this.doResolveRace(iterables); return this; }, /** * @param {function(...*):*=} fulfilledFunction * @param {function(...*):*=} rejectedFunction * @return {Promise} */ then: function(fulfilledFunction, rejectedFunction) { var handler = this.generateThenHandler(fulfilledFunction, rejectedFunction); this.processHandlers(); return handler.getForwardPromise(); }, //------------------------------------------------------------------------------- // Private Methods //------------------------------------------------------------------------------- /** * @protected * @param {Array<*>} values */ doFulfillPromise: function(values) { if (this.isPending()) { this.resolving = false; this.state = Promise.State.FULFILLED; this.valueList.addAll(values); this.processHandlers(); } else { throw Throwables.bug('PromiseAlreadyResolved', 'Promise has already been resolved'); } }, /** * @private */ doProcessHandlers: function() { var _this = this; this.processing = true; //NOTE BRN: This setTimeout fulfills the 2.2.4 portion of the Promises A+ spec //2.2.4: onFulfilled or onRejected must not be called until the execution context stack contains only platform code. setTimeout($trace(function() { while (_this.processIndex < _this.handlerList.getCount()) { _this.processHandler(_this.processIndex); _this.processIndex++; } _this.processing = false; }), 0); }, /** * @protected * @param {Array<*>} reasons */ doRejectPromise: function(reasons) { if (this.isPending()) { reasons.forEach(function(reason) { $error(reason); }); this.resolving = false; this.state = Promise.State.REJECTED; this.reasonList.addAll(reasons); this.processHandlers(); } else { throw Throwables.bug('PromiseAlreadyResolved', 'Promise has already been resolved'); } }, /** * @private */ doResolve: function() { var _this = this; this.resolving = true; this.resolver.resolve(function(values) { _this.doFulfillPromise(values); }, function(reasons) { _this.doRejectPromise(reasons); }); }, /** * @private * @param {Array<(Array<*> | IIterable<*>)>} iterables */ doResolveAll: function(iterables) { this.resolver = Resolvers.resolveAll([this], iterables); this.doResolve(); }, /** * @private * @param {Array<(Object<*, *> | IMap<*, *>)>} objects */ doResolveProps: function(objects) { this.resolver = Resolvers.resolveProps([this], objects); this.doResolve(); }, /** * @private * @param {Array<(Array<*> | IIterable<*>)>} iterables */ doResolveRace: function(iterables) { this.resolver = Resolvers.resolveRace([this], iterables); this.doResolve(); }, /** * @private * @param {Array<*>} values */ doResolveValues: function(values) { this.resolver = Resolvers.resolveValues([this], values); this.doResolve(); }, /** * @private * @param {function(Throwable, *...):*} callbackFunction * @param {Promise} forwardPromise * @return {CallbackHandler} */ factoryCallbackHandler: function(callbackFunction, forwardPromise) { return new CallbackHandler(callbackFunction, forwardPromise); }, /** * @private * @param {function(*...):*} catchFunction * @param {Promise} forwardPromise * @return {CatchHandler} */ factoryCatchHandler: function(catchFunction, forwardPromise) { return new CatchHandler(catchFunction, forwardPromise); }, /** * @private * @param {function(*...):*} finallyFunction * @param {Promise} forwardPromise * @return {FinallyHandler} */ factoryFinallyHandler: function(finallyFunction, forwardPromise) { return new FinallyHandler(finallyFunction, forwardPromise); }, /** * @private * @param {function(*...):*} fulfilledFunction * @param {function(*...):*} rejectedFunction * @param {Promise} forwardPromise * @return {ThenHandler} */ factoryThenHandler: function(fulfilledFunction, rejectedFunction, forwardPromise) { return new ThenHandler(fulfilledFunction, rejectedFunction, forwardPromise); }, /** * @private * @param {function(Throwable, *...):*} callbackFunction * @return {CallbackHandler} */ generateCallbackHandler: function(callbackFunction) { var forwardPromise = new Promise(); var handler = this.factoryCallbackHandler(callbackFunction, forwardPromise); this.handlerList.add(handler); return handler; }, /** * @private * @param {function(*...):*} catchFunction * @return {CatchHandler} */ generateCatchHandler: function(catchFunction) { var forwardPromise = new Promise(); var handler = this.factoryCatchHandler(catchFunction, forwardPromise); this.handlerList.add(handler); return handler; }, /** * @private * @param {function(*...):*} finallyFunction * @return {FinallyHandler} */ generateFinallyHandler: function(finallyFunction) { var forwardPromise = new Promise(); var handler = this.factoryFinallyHandler(finallyFunction, forwardPromise); this.handlerList.add(handler); return handler; }, /** * @private * @param {function(*...):*} fulfilledFunction * @param {function(*...):*} rejectedFunction * @return {ThenHandler} */ generateThenHandler: function(fulfilledFunction, rejectedFunction) { var forwardPromise = new Promise(); var handler = this.factoryThenHandler(fulfilledFunction, rejectedFunction, forwardPromise); this.handlerList.add(handler); return handler; }, /** * @private * @param {number} index */ processHandler: function(index) { var handler = this.handlerList.getAt(index); if (this.isFulfilled()) { var values = this.valueList.toArray(); handler.handleFulfilled(values); } else { var reasons = this.reasonList.toArray(); handler.handleRejected(reasons); } }, /** * @private */ processHandlers: function() { if (!this.isPending() && !this.isProcessing()) { this.doProcessHandlers(); } }, /** * @private * @param {Array.<(Array.<*> | IIterable<*>)>} iterables */ validateIterables: function(iterables) { iterables.forEach(function(iterable) { if (!TypeUtil.isArray(iterable) && !Class.doesImplement(iterable, IIterable)) { throw Throwables.bug('TypeError', {}, 'Expecting an array or an iterable object but got ' + iterable); } }) }, /** * @private * @param {Array<(Object<*, *> | IMap<*, *>)>} objects * @throws {Bug} */ validateObjects: function(objects) { objects.forEach(function(object) { if (!TypeUtil.isObject(object) && !Class.doesImplement(object, IMap)) { throw Throwables.bug('TypeError', {}, 'Expecting an object or an IMap but got ' + object); } }) }, /** * @private * @throws {Bug} */ validatePromiseState: function() { if (!this.isPending()) { throw Throwables.bug('IllegalState', {}, 'Promise is no longer pending. Cannot resolve a promise that is not pending.'); } if (this.isResolving()) { throw Throwables.bug('IllegalState', {}, 'Promise is already resolving. Cannot resolve a promise that is already resolving.'); } } }); //------------------------------------------------------------------------------- // Implement Interfaces //------------------------------------------------------------------------------- Class.implement(Promise, IPromise); //------------------------------------------------------------------------------- // Static Properties //------------------------------------------------------------------------------- /** * @static * @enum {string} */ Promise.State = { FULFILLED: 'fulfilled', PENDING: 'pending', REJECTED: 'rejected' }; //------------------------------------------------------------------------------- // Static Mehtods //------------------------------------------------------------------------------- /** * @static * @param {...(Array<*> | IIterable<*>)} iterable * @return {Promise} */ Promise.all = function(iterable) { return Promise.promise() .resolveAll(ArgUtil.toArray(arguments)); }; /** * @static * @param {function(function(...*), function(...*))=} promiseMethod * @return {Promise} */ Promise.promise = function(promiseMethod) { return new Promise(promiseMethod); }; /** * @static * @param {...(Object<*, *> | IMap<*, *>)} object * @return {Promise} */ Promise.props = function(object) { return Promise.promise() .resolveProps(ArgUtil.toArray(arguments)); }; /** * @static * @param {...(Array<*> | IIterable<*>)} iterable * @return {Promise} */ Promise.race = function(iterable) { return Promise.promise() .resolveRace(ArgUtil.toArray(arguments)); }; /** * @static * @param {...*} reason * @return {Promise} */ Promise.reject = function(reason) { return Promise.promise() .reject(ArgUtil.toArray(arguments)); }; /** * @static * @param {...*} value * @returns {Promise} */ Promise.resolve = function(value) { return Promise.promise() .resolve(ArgUtil.toArray(arguments)); }; /** * @static * @param {function(...*):*} promiseMethod * @returns {Promise} */ Promise.try = function(promiseMethod) { return Promise.promise() .resolve([]) .then(promiseMethod); }; //------------------------------------------------------------------------------- // Exports //------------------------------------------------------------------------------- bugpack.export('Promise', Promise); });