UNPKG

@barchart/common-js

Version:
265 lines (259 loc) 10.2 kB
var __create = Object.create; var __defProp = Object.defineProperty; var __getOwnPropDesc = Object.getOwnPropertyDescriptor; var __getOwnPropNames = Object.getOwnPropertyNames; var __getProtoOf = Object.getPrototypeOf; var __hasOwnProp = Object.prototype.hasOwnProperty; var __export = (target, all) => { for (var name in all) __defProp(target, name, { get: all[name], enumerable: true }); }; var __copyProps = (to, from, except, desc) => { if (from && typeof from === "object" || typeof from === "function") { for (let key of __getOwnPropNames(from)) if (!__hasOwnProp.call(to, key) && key !== except) __defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable }); } return to; }; var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__getProtoOf(mod)) : {}, __copyProps( // If the importer is in node compatibility mode or this is not an ESM // file that has been converted to a CommonJS file using a Babel- // compatible transform (i.e. "__esModule" has not been set), then set // "default" to the CommonJS "module.exports" for node compatibility. isNodeMode || !mod || !mod.__esModule ? __defProp(target, "default", { value: mod, enumerable: true }) : target, mod )); var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod); var Scheduler_exports = {}; __export(Scheduler_exports, { default: () => Scheduler }); module.exports = __toCommonJS(Scheduler_exports); var assert = __toESM(require("./../lang/assert.js")); var is = __toESM(require("./../lang/is.js")); var object = __toESM(require("./../lang/object.js")); var promise = __toESM(require("./../lang/promise.js")); var import_Disposable = __toESM(require("./../lang/Disposable.js")); class Scheduler extends import_Disposable.default { #timeoutBindings; #intervalBindings; constructor() { super(); this.#timeoutBindings = {}; this.#intervalBindings = {}; } /** * Schedules an action to execute in the future, returning a Promise. * * @public * @async * @param {Function} actionToSchedule - The action to execute. * @param {number} millisecondDelay - Milliseconds before the action can be started. * @param {string=} actionDescription - A description of the action, used for logging purposes. * @returns {Promise} */ async schedule(actionToSchedule, millisecondDelay, actionDescription) { assert.argumentIsRequired(actionToSchedule, "actionToSchedule", Function); assert.argumentIsRequired(millisecondDelay, "millisecondDelay", Number); assert.argumentIsOptional(actionDescription, "actionDescription", String); if (this.disposed) { throw new Error("The Scheduler has been disposed."); } let token; const schedulePromise = promise.build((resolveCallback, rejectCallback) => { const wrappedAction = () => { const disposable = this.#timeoutBindings[token]; if (disposable) { disposable.dispose(); } try { resolveCallback(actionToSchedule()); } catch (e) { rejectCallback(e); } }; token = setTimeout(wrappedAction, millisecondDelay); this.#timeoutBindings[token] = import_Disposable.default.fromAction(() => { clearTimeout(token); delete this.#timeoutBindings[token]; }); }); return schedulePromise; } /** * @public * @param {Function} actionToRepeat * @param {number} millisecondInterval * @param {string=} actionDescription * @returns {Disposable} */ repeat(actionToRepeat, millisecondInterval, actionDescription) { assert.argumentIsRequired(actionToRepeat, "actionToRepeat", Function); assert.argumentIsRequired(millisecondInterval, "millisecondInterval", Number); assert.argumentIsOptional(actionDescription, "actionDescription", String); if (this.disposed) { throw new Error("The Scheduler has been disposed."); } const wrappedAction = () => { try { actionToRepeat(); } catch { } }; const token = setInterval(wrappedAction, millisecondInterval); this.#intervalBindings[token] = import_Disposable.default.fromAction(() => { clearInterval(token); delete this.#intervalBindings[token]; }); return this.#intervalBindings[token]; } /** * Attempts an action, repeating if necessary, using an exponential backoff. * * @public * @async * @param {Function} actionToBackoff - The action to attempt. If it fails -- because an error is thrown, a promise is rejected, or the function returns a falsey value -- the action will be invoked again. * @param {number=} millisecondDelay - The amount of time to wait to execute the action. Subsequent failures are multiply this value by 2 ^ [number of failures]. So, a 1000 millisecond backoff would schedule attempts using the following delays: 0, 1000, 2000, 4000, 8000, etc. If not specified, the first attempt will execute immediately, then a value of 1000 will be used. * @param {string=} actionDescription - Description of the action to attempt, used for logging purposes. * @param {number=} maximumAttempts - The number of attempts to before giving up. * @param {Function=} failureCallback - If provided, will be invoked if a function is considered to be failing. * @param {object=} failureValue - If provided, will consider the result to have failed, if this value is returned (a deep equality check is used). If not provided, an undefined value will trigger a retry. * @param {number=} maximumDelay - The maximum delay that can be used for the backoff. If not provided, the delay will continue to double until the maximum number of attempts is reached. * @returns {Promise} */ async backoff(actionToBackoff, millisecondDelay, actionDescription, maximumAttempts, failureCallback, failureValue, maximumDelay) { assert.argumentIsRequired(actionToBackoff, "actionToBackoff", Function); assert.argumentIsOptional(millisecondDelay, "millisecondDelay", Number); assert.argumentIsOptional(actionDescription, "actionDescription", String); assert.argumentIsOptional(maximumAttempts, "maximumAttempts", Number); assert.argumentIsOptional(failureCallback, "failureCallback", Function); assert.argumentIsOptional(maximumDelay, "maximumDelay", Number); if (this.disposed) { throw new Error("The Scheduler has been disposed."); } const processAction = async (attempts2) => { let delay; if (attempts2 === 0) { delay = 0; } else { delay = (millisecondDelay || 1e3) * Math.pow(2, attempts2 - 1); if (maximumDelay && delay > maximumDelay) { delay = maximumDelay; } } try { let result; if (delay === 0) { result = await actionToBackoff(); } else { result = await this.schedule(actionToBackoff, delay, `Attempt [ ${attempts2} ] for [ ${actionDescription || "unnamed action"} ]`); } if (!is.undef(failureValue) && object.equals(result, failureValue)) { throw `Attempt [ ${attempts2} ] for [ ${actionDescription || "unnamed action"} ] failed due to invalid result`; } return result; } catch (e) { if (is.fn(failureCallback)) { failureCallback(attempts2); } throw e; } }; let attempts = 0; const processActionRecursive = async () => { try { const result = await processAction(attempts++); return result; } catch (e) { if (maximumAttempts > 0 && attempts === maximumAttempts) { const message = `Maximum failures reached for ${actionDescription || "unnamed action"}`; if (is.object(e)) { e.backoff = message; throw e; } throw message; } return processActionRecursive(); } }; return processActionRecursive(); } /** * @protected * @override */ _onDispose() { object.keys(this.#timeoutBindings).forEach((key) => { this.#timeoutBindings[key].dispose(); }); object.keys(this.#intervalBindings).forEach((key) => { this.#intervalBindings[key].dispose(); }); this.#timeoutBindings = null; this.#intervalBindings = null; } /** * @public * @static * @async * @param {Function} actionToSchedule * @param {number} millisecondDelay * @param {string=} actionDescription * @returns {Promise} */ static async schedule(actionToSchedule, millisecondDelay, actionDescription) { const scheduler = new Scheduler(); let result; try { result = await scheduler.schedule(actionToSchedule, millisecondDelay, actionDescription); } finally { scheduler.dispose(); } return result; } /** * @public * @static * @async * @param {Function} actionToBackoff * @param {number} millisecondDelay * @param {string=} actionDescription * @param {number=} maximumAttempts * @param {Function=} failureCallback * @param {object=} failureValue * @param {number=} maximumDelay * @returns {Promise} */ static async backoff(actionToBackoff, millisecondDelay, actionDescription, maximumAttempts, failureCallback, failureValue, maximumDelay) { const scheduler = new Scheduler(); let result; try { result = await scheduler.backoff(actionToBackoff, millisecondDelay, actionDescription, maximumAttempts, failureCallback, failureValue, maximumDelay); } finally { scheduler.dispose(); } return result; } /** * Returns a string representation. * * @public * @returns {string} */ toString() { return "[Scheduler]"; } } { const cjsExports = module.exports; const cjsDefaultExport = cjsExports && cjsExports.__esModule ? cjsExports.default : cjsExports; if (cjsDefaultExport && (typeof cjsDefaultExport === 'function' || typeof cjsDefaultExport === 'object')) { Object.keys(cjsExports).forEach((key) => { if (key !== 'default' && key !== '__esModule') { cjsDefaultExport[key] = cjsExports[key]; } }); } module.exports = cjsDefaultExport; }