UNPKG

custom-idle-queue

Version:

Optimize the speed of important tasks on limited ressources

290 lines (232 loc) 6.75 kB
"use strict"; Object.defineProperty(exports, "__esModule", { value: true }); exports.IdleQueue = void 0; /** * Creates a new Idle-Queue * @constructor * @param {number} [parallels=1] amount of parrallel runs of the limited-ressource */ var IdleQueue = function IdleQueue() { var parallels = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : 1; this._parallels = parallels || 1; /** * _queueCounter * each lock() increased this number * each unlock() decreases this number * If _qC==0, the state is in idle * @type {Number} */ this._qC = 0; /** * _idleCalls * contains all promises that where added via requestIdlePromise() * and not have been resolved * @type {Set<Promise>} _iC with oldest promise first */ this._iC = new Set(); /** * _lastHandleNumber * @type {Number} */ this._lHN = 0; /** * _handlePromiseMap * Contains the handleNumber on the left * And the assigned promise on the right. * This is stored so you can use cancelIdleCallback(handleNumber) * to stop executing the callback. * @type {Map<Number><Promise>} */ this._hPM = new Map(); this._pHM = new Map(); // _promiseHandleMap }; exports.IdleQueue = IdleQueue; IdleQueue.prototype = { isIdle: function isIdle() { return this._qC < this._parallels; }, /** * creates a lock in the queue * and returns an unlock-function to remove the lock from the queue * @return {function} unlock function than must be called afterwards */ lock: function lock() { this._qC++; }, unlock: function unlock() { this._qC--; _tryIdleCall(this); }, /** * wraps a function with lock/unlock and runs it * @param {function} fun * @return {Promise<any>} */ wrapCall: function wrapCall(fun) { var _this = this; this.lock(); var maybePromise; try { maybePromise = fun(); } catch (err) { this.unlock(); throw err; } if (!maybePromise.then || typeof maybePromise.then !== 'function') { // no promise this.unlock(); return maybePromise; } else { // promise return maybePromise.then(function (ret) { // sucessfull -> unlock before return _this.unlock(); return ret; })["catch"](function (err) { // not sucessfull -> unlock before throwing _this.unlock(); throw err; }); } }, /** * does the same as requestIdleCallback() but uses promises instead of the callback * @param {{timeout?: number}} options like timeout * @return {Promise<void>} promise that resolves when the database is in idle-mode */ requestIdlePromise: function requestIdlePromise(options) { var _this2 = this; options = options || {}; var resolve; var prom = new Promise(function (res) { return resolve = res; }); var resolveFromOutside = function resolveFromOutside() { _removeIdlePromise(_this2, prom); resolve(); }; prom._manRes = resolveFromOutside; if (options.timeout) { // if timeout has passed, resolve promise even if not idle var timeoutObj = setTimeout(function () { prom._manRes(); }, options.timeout); prom._timeoutObj = timeoutObj; } this._iC.add(prom); _tryIdleCall(this); return prom; }, /** * remove the promise so it will never be resolved * @param {Promise} promise from requestIdlePromise() * @return {void} */ cancelIdlePromise: function cancelIdlePromise(promise) { _removeIdlePromise(this, promise); }, /** * api equal to * @link https://developer.mozilla.org/en-US/docs/Web/API/Window/requestIdleCallback * @param {Function} callback * @param {options} options [description] * @return {number} handle which can be used with cancelIdleCallback() */ requestIdleCallback: function requestIdleCallback(callback, options) { var handle = this._lHN++; var promise = this.requestIdlePromise(options); this._hPM.set(handle, promise); this._pHM.set(promise, handle); promise.then(function () { return callback(); }); return handle; }, /** * API equal to * @link https://developer.mozilla.org/en-US/docs/Web/API/Window/cancelIdleCallback * @param {number} handle returned from requestIdleCallback() * @return {void} */ cancelIdleCallback: function cancelIdleCallback(handle) { var promise = this._hPM.get(handle); this.cancelIdlePromise(promise); }, /** * clears and resets everything * @return {void} */ clear: function clear() { var _this3 = this; // remove all non-cleared this._iC.forEach(function (promise) { return _removeIdlePromise(_this3, promise); }); this._qC = 0; this._iC.clear(); this._hPM = new Map(); this._pHM = new Map(); } }; /** * processes the oldest call of the idleCalls-queue * @return {Promise<void>} */ function _resolveOneIdleCall(idleQueue) { if (idleQueue._iC.size === 0) return; var iterator = idleQueue._iC.values(); var oldestPromise = iterator.next().value; oldestPromise._manRes(); // try to call the next tick setTimeout(function () { return _tryIdleCall(idleQueue); }, 0); } /** * removes the promise from the queue and maps and also its corresponding handle-number * @param {Promise} promise from requestIdlePromise() * @return {void} */ function _removeIdlePromise(idleQueue, promise) { if (!promise) return; // remove timeout if exists if (promise._timeoutObj) clearTimeout(promise._timeoutObj); // remove handle-nr if exists if (idleQueue._pHM.has(promise)) { var handle = idleQueue._pHM.get(promise); idleQueue._hPM["delete"](handle); idleQueue._pHM["delete"](promise); } // remove from queue idleQueue._iC["delete"](promise); } /** * resolves the last entry of this._iC * but only if the queue is empty * @return {Promise} */ function _tryIdleCall(idleQueue) { // ensure this does not run in parallel if (idleQueue._tryIR || idleQueue._iC.size === 0) return; idleQueue._tryIR = true; // w8 one tick setTimeout(function () { // check if queue empty if (!idleQueue.isIdle()) { idleQueue._tryIR = false; return; } /** * wait 1 tick here * because many functions do IO->CPU->IO * which means the queue is empty for a short time * but the ressource is not idle */ setTimeout(function () { // check if queue still empty if (!idleQueue.isIdle()) { idleQueue._tryIR = false; return; } // ressource is idle _resolveOneIdleCall(idleQueue); idleQueue._tryIR = false; }, 0); }, 0); }