UNPKG

pendings

Version:
326 lines (286 loc) 8.31 kB
/** * Controls single pending promise. */ 'use strict'; var _createClass = function () { function defineProperties(target, props) { for (var i = 0; i < props.length; i++) { var descriptor = props[i]; descriptor.enumerable = descriptor.enumerable || false; descriptor.configurable = true; if ("value" in descriptor) descriptor.writable = true; Object.defineProperty(target, descriptor.key, descriptor); } } return function (Constructor, protoProps, staticProps) { if (protoProps) defineProperties(Constructor.prototype, protoProps); if (staticProps) defineProperties(Constructor, staticProps); return Constructor; }; }(); function _classCallCheck(instance, Constructor) { if (!(instance instanceof Constructor)) { throw new TypeError("Cannot call a class as a function"); } } var TimeoutError = require('./timeout-error'); var _require = require('./utils'), mergeOptions = _require.mergeOptions; var AUTO_RESET = { NEVER: 'never', FULFILLED: 'fulfilled', RESOLVED: 'resolved', REJECTED: 'rejected' }; var DEFAULT_OPTIONS = { timeout: 0, autoReset: AUTO_RESET.NEVER }; var Pending = function () { /** * Creates instance of single pending promise. It holds `resolve / reject` callbacks for future fulfillment. * @param {Object} [options] * @param {Number} [options.timeout=0] * @param {String} [options.autoReset='never'] automatically reset pending to initial state. * Possible values are: `never`, `fufilled`, `resolved`, `rejected`. */ function Pending(options) { _classCallCheck(this, Pending); this._options = mergeOptions(DEFAULT_OPTIONS, options); this._resolve = null; this._reject = null; // it seems that isPending can be calculated as `Boolean(this._promise) && !this.isFulfilled`, // but it is not true: if call(fn) throws error in the same tick, `this._promise` is yet undefined. this._isPending = false; this._isResolved = false; this._isRejected = false; this._value = undefined; this._promise = null; this._timer = null; this._onFulfilled = function () {}; } /** * Returns promise itself. * * @returns {Promise} */ _createClass(Pending, [{ key: 'call', /** * For the first time this method calls `fn` and returns new promise. Also holds `resolve` / `reject` callbacks * to allow fulfill promise via `pending.resolve()` and `pending.reject()`. All subsequent calls of `.call(fn)` * will return the same promise, which can be still pending or already fulfilled. * To reset this behavior use `.reset()`. If `options.timeout` is specified, the promise will be automatically * rejected after `timeout` milliseconds with `TimeoutError`. * * @param {Function} fn * @param {Object} [options] * @param {Number} [options.timeout=0] timeout after which promise will be automatically rejected * @returns {Promise} */ value: function call(fn, options) { if (this._isPending || this.isFulfilled) { return this._promise; } else { this.reset(); this._callOptions = mergeOptions(this._options, options); this._initPromise(fn); this._initTimer(); return this._promise; } } /** * Resolves pending promise with specified `value`. * * @param {*} [value] */ }, { key: 'resolve', value: function resolve(value) { if (this._isPending) { this._preFulfill(); this._isResolved = true; this._value = value; this._resolve(value); this._postFulfill(); } } /** * Rejects pending promise with specified `value`. * * @param {*} [value] */ }, { key: 'reject', value: function reject(value) { if (this._isPending) { this._preFulfill(); this._isRejected = true; this._value = value; this._reject(value); this._postFulfill(); } } /** * Helper method: rejects if `rejectValue` is truthy, otherwise resolves with `resolveValue`. * * @param {*} [resolveValue] * @param {*} [rejectValue] */ }, { key: 'fulfill', value: function fulfill(resolveValue, rejectValue) { if (rejectValue) { this.reject(rejectValue); } else { this.resolve(resolveValue); } } /** * Resets to initial state. * * @param {Error} [error] custom rejection error if promise is in pending state. */ }, { key: 'reset', value: function reset(error) { if (this._isPending) { error = error || new Error('Promise rejected by reset'); this.reject(error); } this._promise = null; this._isPending = false; this._isResolved = false; this._isRejected = false; this._value = undefined; this._clearTimer(); } }, { key: '_initPromise', value: function _initPromise(fn) { var _this = this; this._promise = new Promise(function (resolve, reject) { _this._isPending = true; _this._resolve = resolve; _this._reject = reject; if (typeof fn === 'function') { _this._callFn(fn); } }); } }, { key: '_initTimer', value: function _initTimer() { var _this2 = this; var timeout = this._callOptions.timeout; if (timeout) { this._timer = setTimeout(function () { return _this2.reject(new TimeoutError(timeout)); }, timeout); } } }, { key: '_clearTimer', value: function _clearTimer() { if (this._timer) { clearTimeout(this._timer); this._timer = null; } } }, { key: '_callFn', value: function _callFn(fn) { try { var res = fn(); this._attachToFnPromise(res); } catch (e) { this.reject(e); } } }, { key: '_attachToFnPromise', value: function _attachToFnPromise(p) { var _this3 = this; if (p && typeof p.then === 'function') { p.then(function (value) { return _this3.resolve(value); }, function (e) { return _this3.reject(e); }); } } }, { key: '_preFulfill', value: function _preFulfill() { this._isPending = false; this._clearTimer(); } }, { key: '_postFulfill', value: function _postFulfill() { this._onFulfilled(this); this._applyAutoReset(); } }, { key: '_applyAutoReset', value: function _applyAutoReset() { var autoReset = this._options.autoReset; var needReset = [autoReset === AUTO_RESET.FULFILLED, this._isResolved && autoReset === AUTO_RESET.RESOLVED, this._isRejected && autoReset === AUTO_RESET.REJECTED].some(Boolean); if (needReset) { this.reset(); } } }, { key: 'promise', get: function get() { return this._promise; } /** * Returns value with that promise was fulfilled (resolved or rejected). * * @returns {*} */ }, { key: 'value', get: function get() { return this._value; } /** * Returns true if promise resolved. * * @returns {Boolean} */ }, { key: 'isResolved', get: function get() { return this._isResolved; } /** * Returns true if promise rejected. * * @returns {Boolean} */ }, { key: 'isRejected', get: function get() { return this._isRejected; } /** * Returns true if promise fulfilled (resolved or rejected). * * @returns {Boolean} */ }, { key: 'isFulfilled', get: function get() { return this._isResolved || this._isRejected; } /** * Returns true if promise is pending. * * @returns {Boolean} */ }, { key: 'isPending', get: function get() { return this._isPending; } /** * Callback called when promise is fulfilled (resolved or rejected). * * @param {Function} fn */ }, { key: 'onFulfilled', set: function set(fn) { if (typeof fn === 'function') { this._onFulfilled = fn; } else { throw new Error('onFulfilled should be a function.'); } } }]); return Pending; }(); module.exports = Pending;