@barchart/common-js
Version:
Library of common JavaScript utilities
265 lines (259 loc) • 10.2 kB
JavaScript
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;
}