UNPKG

pendings

Version:
321 lines (275 loc) 8.34 kB
/** * Controls list of pending promises. */ '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 Pending = require('./pending'); var _require = require('./utils'), mergeOptions = _require.mergeOptions; var DEFAULT_OPTIONS = { autoRemove: false, timeout: 0, idPrefix: '' }; var Pendings = function () { /** * Manipulation of list of promises. * * @param {Object} [options] * @param {Number} [options.autoRemove=false] automatically remove fulfilled promises from list * @param {Number} [options.timeout=0] default timeout for all promises * @param {String} [options.idPrefix=''] prefix for generated promise IDs */ function Pendings(options) { _classCallCheck(this, Pendings); this._options = mergeOptions(DEFAULT_OPTIONS, options); this._map = Object.create(null); this._waitingAll = new Pending({ autoReset: 'never' }); } /** * Returns count of pending / fulfilled promises in the list. * * @returns {Number} */ _createClass(Pendings, [{ key: 'add', /** * Calls `fn` and returns new promise. `fn` gets generated unique `id` as parameter. * * @param {Function} fn * @param {Object} [options] * @param {Number} [options.timeout] custom timeout for particular promise * @returns {Promise} */ value: function add(fn, options) { var id = this.generateId(); return this.set(id, fn, options); } /** * Calls `fn` and returns new promise with specified `id`. * If promise with such `id` already pending - it will be returned. * * @param {String} id * @param {Function} fn * @param {Object} [options] * @param {Number} [options.timeout=0] custom timeout for particular promise * @returns {Promise} */ }, { key: 'set', value: function set(id, fn, options) { var _this = this; if (this.has(id)) { return this._map[id].promise; } else { var _mergeOptions = mergeOptions(this._options, options), timeout = _mergeOptions.timeout; var pending = this._map[id] = new Pending({ timeout: timeout }); pending.onFulfilled = function () { return _this._onFulfilled(id); }; return pending.call(function () { return fn(id); }); } } /** * Checks if promise with specified `id` is exists in the list. * * @param {String} id * @returns {Boolean} */ }, { key: 'has', value: function has(id) { return Boolean(this._map[id]); } /** * Resolves pending promise by `id` with specified `value`. * Throws if promise does not exist or is already fulfilled. * * @param {String} id * @param {*} [value] */ }, { key: 'resolve', value: function resolve(id, value) { if (this.has(id)) { this._map[id].resolve(value); } else { throw createNoPendingError(id); } } /** * Rejects pending promise by `id` with specified `value`. * Throws if promise does not exist or is already fulfilled. * * @param {String} id * @param {*} [value] */ }, { key: 'reject', value: function reject(id, value) { if (this.has(id)) { this._map[id].reject(value); } else { throw createNoPendingError(id); } } /** * Rejects pending promise by `id` if `reason` is truthy, otherwise resolves with `value`. * Throws if promise does not exist or is already fulfilled. * * @param {String} id * @param {*} [resolveValue] * @param {*} [rejectValue] */ }, { key: 'fulfill', value: function fulfill(id, resolveValue, rejectValue) { if (this.has(id)) { this._map[id].fulfill(resolveValue, rejectValue); } else { throw createNoPendingError(id); } } /** * Resolves pending promise by `id` with specified `value` if it exists. * * @param {String} id * @param {*} [value] */ }, { key: 'tryResolve', value: function tryResolve(id, value) { if (this.has(id)) { this._map[id].resolve(value); } } /** * Rejects pending promise by `id` with specified `value` if it exists. * * @param {String} id * @param {*} [value] */ }, { key: 'tryReject', value: function tryReject(id, value) { if (this.has(id)) { this._map[id].reject(value); } } /** * Rejects pending promise by `id` if `reason` is truthy, otherwise resolves with `value`. * * @param {String} id * @param {*} [resolveValue] * @param {*} [rejectValue] */ }, { key: 'tryFulfill', value: function tryFulfill(id, resolveValue, rejectValue) { if (this.has(id)) { this._map[id].fulfill(resolveValue, rejectValue); } } /** * Rejects all pending promises with specified `value`. Useful for cleanup. * * @param {*} [value] */ }, { key: 'rejectAll', value: function rejectAll(value) { var _this2 = this; Object.keys(this._map).forEach(function (id) { return _this2.tryReject(id, value); }); } /** * Waits for all promises to fulfill and returns object with resolved/rejected values. * * @returns {Promise} promise resolved with object `{resolved: {id: value, ...}, rejected: {id: value, ...}}` */ }, { key: 'waitAll', value: function waitAll() { var _this3 = this; return this._waitingAll.call(function () { return _this3._checkAllFulfilled(); }); } /** * Removes all items from list. * If there is waitAll promise - it will be resolved with empty results. */ }, { key: 'clear', value: function clear() { this._map = Object.create(null); if (this._waitingAll.isPending) { this._checkAllFulfilled(); } } /** * Generates unique ID. Can be overwritten. * * @returns {String} */ }, { key: 'generateId', value: function generateId() { return '' + this._options.idPrefix + Date.now() + '-' + Math.random(); } }, { key: '_onFulfilled', value: function _onFulfilled(id) { if (this._options.autoRemove) { delete this._map[id]; } if (this._waitingAll.isPending) { this._checkAllFulfilled(); } } }, { key: '_getTimeout', value: function _getTimeout(options) { return options && options.timeout !== undefined ? options.timeout : this._options.timeout; } }, { key: '_checkAllFulfilled', value: function _checkAllFulfilled() { var _this4 = this; var allFulfilled = Object.keys(this._map).every(function (id) { return _this4._map[id].isFulfilled; }); if (allFulfilled) { var result = this._getAllValues(); this._waitingAll.resolve(result); this._waitingAll.reset(); } } }, { key: '_getAllValues', value: function _getAllValues() { var _this5 = this; var result = { resolved: {}, rejected: {} }; Object.keys(this._map).forEach(function (id) { var pending = _this5._map[id]; result[pending.isResolved ? 'resolved' : 'rejected'][id] = pending.value; }); return result; } }, { key: 'count', get: function get() { return Object.keys(this._map).length; } }]); return Pendings; }(); function createNoPendingError(id) { return new Error('Pending promise not found with id: ' + id); } module.exports = Pendings;