pendings
Version:
Better control of promises
321 lines (275 loc) • 8.34 kB
JavaScript
/**
* 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;