@creejs/commons-retrier
Version:
Common Utils About Task Retrying
1,030 lines (933 loc) • 72.5 kB
JavaScript
(function (global, factory) {
typeof exports === 'object' && typeof module !== 'undefined' ? factory(exports) :
typeof define === 'function' && define.amd ? define(['exports'], factory) :
(global = typeof globalThis !== 'undefined' ? globalThis : global || self, factory(global.CommonsLang = {}));
})(this, (function (exports) { 'use strict';
let e$1 = class e extends Error{static isAggregatedErrorLike(t){return t&&Array.isArray(t.errors)}static isAggregatedError(t){return t instanceof e}constructor(t,r){super(t),this.errors=r??[];}addError(t){this.errors.push(t);}removeError(t){const r=this.errors.indexOf(t);return -1!==r&&(this.errors.splice(r,1),true)}};var n$1={get:function(t,r,e){if(null==t)return e;if("string"!=typeof r)throw new Error('"path" must be a string');const n=r.split(".");let o=t;for(const t of n){if(void 0===o[t])return e;o=o[t];}return o},constructorName:o$1,defaults:function(t,...r){if(null==t)throw new TypeError('"target" Should Not Nil');for(const e of r)if(null!=e)for(const r in e) void 0===t[r]&&(t[r]=e[r]);return t},extend:i$1,extends:i$1,equals:function(t,r){if(t===r)return true;if("function"==typeof t?.equals)return t.equals(r);if("function"==typeof r?.equals)return r.equals(t);return false},isBrowser:s$1,isNode:function(){return !s$1()},cloneToPlainObject:function(t){if(null==t)return t;if("object"!=typeof t)throw new Error("Only Object allowed to clone");return {...t}},deepCloneToPlainObject:function(t){if(null==t)return t;if("object"!=typeof t)throw new Error("Only Object allowed to clone");return JSON.parse(JSON.stringify(t))}};function o$1(t){return t?.constructor?.name}function i$1(t,...r){if(null==t)throw new TypeError('"target" must not be null or undefined');for(const e of r)if(null!=e)for(const r in e)t[r]=e[r];return t}function s$1(){return "undefined"!=typeof window&&"undefined"!=typeof document}var u$1={isArray:f$1,isBoolean:a$1,isBuffer:function(t){return null!=t&&Buffer.isBuffer(t)},isFunction:c$1,isInstance:l$1,isIterable:y,isDate:function(t){return null!=t&&t instanceof Date},isError:function(t){return null!=t&&t instanceof Error},isMap:function(t){return null!=t&&"object"==typeof t&&t.constructor===Map},isWeakMap:function(t){return null!=t&&"object"==typeof t&&t.constructor===WeakMap},isNumber:b$1,isPositive:h$1,isNegative:w$1,isNotNegative:g$1,isNil:p$1,isNullOrUndefined:function(t){return null==t},isNull:d$1,isUndefined:m$1,isPlainObject:E,isObject:A,isPromise:N,isRegExp:function(t){return null!=t&&"object"==typeof t&&t.constructor===RegExp},isSet:function(t){return null!=t&&"object"==typeof t&&t.constructor===Set},isWeakSet:function(t){return null!=t&&"object"==typeof t&&t.constructor===WeakSet},isStream:function(t){return null!=t&&"function"==typeof t.pipe},isString:$,isSymbol:v$1,isPrimitive:function(t){return null!==t&&("string"==typeof t||"number"==typeof t||"boolean"==typeof t)},isInt8Array:P,isUint8Array:j,isUint8ClampedArray:S,isInt16Array:x,isUint16Array:I,isInt32Array:U,isUint32Array:T,isFloat32Array:B,isFloat64Array:k$1,isBigInt64Array:L$1,isBigUint64Array:F,isTypedArray:O$1,isArrayBuffer:C};function f$1(t){return Array.isArray(t)}function a$1(t){return "boolean"==typeof t}function c$1(t){return "function"==typeof t}function l$1(t){return null!=t&&"object"==typeof t&&!E(t)}function y(t){return null!=t&&"function"==typeof t[Symbol.iterator]}function p$1(t){return null==t}function h$1(t){return !!b$1(t)&&t>0}function g$1(t){return !!b$1(t)&&t>=0}function w$1(t){return !!b$1(t)&&t<0}function d$1(t){return null===t}function m$1(t){return void 0===t}function b$1(t){return null!=t&&"number"==typeof t}function A(t){return null!=t&&"object"==typeof t}function E(t){return null!==t&&"object"==typeof t&&(t.constructor===Object||void 0===t.constructor)}function N(t){return null!=t&&"function"==typeof t.then}function $(t){return null!=t&&"string"==typeof t}function v$1(t){return null!=t&&"symbol"==typeof t}function O$1(t){return ArrayBuffer.isView(t)&&t.constructor!==DataView}function P(t){return t instanceof Int8Array}function j(t){return t instanceof Uint8Array}function S(t){return t instanceof Uint8ClampedArray}function x(t){return t instanceof Int16Array}function I(t){return t instanceof Uint16Array}function U(t){return t instanceof Int32Array}function T(t){return t instanceof Uint32Array}function B(t){return t instanceof Float32Array}function k$1(t){return t instanceof Float64Array}function L$1(t){return t instanceof BigInt64Array}function F(t){return t instanceof BigUint64Array}function C(t){return t instanceof ArrayBuffer}var M={assertNumber:_$1,assertPositive:R,assertNotNegative:J,assertFunction:H,assertNotNil:z,assertString:q};function D(t,r){if(!Array.isArray(t))throw new Error(`${r?'"'+r+'" ':""}Not Array: type=${typeof t} value=${tt(t)}`)}function q(t,r){if(!$(t))throw new Error(`${r?'"'+r+'" ':""}Not String: type=${typeof t} value=${tt(t)}`)}function _$1(t,r){if(!b$1(t))throw new Error(`${r?'"'+r+'" ':""}Not Number: type=${typeof t} value=${tt(t)}`)}function R(t,r){if(!h$1(t))throw new Error(`${r?'"'+r+'" ':""}Not Positive: ${t}`)}function J(t,r){if(!g$1(t))throw new Error(`${r?'"'+r+'" ':""}Not "0 or Positive": ${t}`)}function H(t,r){if(!c$1(t))throw new Error(`${r?'"'+r+'" ':""}Not Function: type=${typeof t} value=${tt(t)}`)}function V(t,r){if(!N(t))throw new Error(`${r?'"'+r+'" ':""}Not Promise: type=${typeof t} value=${tt(t)}`)}function z(t,r){if(p$1(t))throw new Error((r?'"'+r+'" ':"")+"Should Not Nil")}function tt(t){if(null===t)return "null";if(void 0===t)return "undefined";const r=typeof t;if("string"===r)return t;if("symbol"===r)return `Symbol(${t.description})`;if("function"===r)return `Function ${t.name}(){}`;if(t instanceof String)return t.toString();if(Number.isNaN(t))return "NaN";if(t===1/0)return "Infinity";if(t===-1/0)return "-Infinity";if(t instanceof Error)return `${t.constructor.name}: ${t.message}`;if(t instanceof Promise)return "Promise";if(t instanceof Set)return `Set: ${tt(Array.from(t))}`;if(t instanceof Map)return `Map: ${tt(Array.from(t.entries()))}`;if(t instanceof RegExp)return t.toString();if(Array.isArray(t))return `[${t.map(tt).join(", ")}]`;let e;try{e=JSON.stringify(t);}catch(r){e=t.toString();}return e}var et={any:function(t){if(D(t),0===t.length)throw new Error("Empty Tasks");const r=nt(),n=[];for(let o=0;o<t.length;o++){const i=t[o];let s;if(u$1.isPromise(i))s=i;else {if(!u$1.isFunction(i)){n.push(new Error(`Invalid Task at index ${o}/${t.length-1}: ${i}`));continue}s=it(i);}s.then(t=>{r.resolve(t);}).catch(o=>{n.push(o),n.length>=t.length&&r.reject(new e$1("All Tasks Failed",n));});}n.length===t.length&&r.reject(new e$1("All Tasks Failed",n));return r.promise},defer:nt,delay:function(t,r){u$1.isNumber(t)?(r=t,t=Promise.resolve()):null==t&&null==r&&(r=1,t=Promise.resolve());null!=t&&V(t),_$1(r=r??1e3);const e=nt(),n=Date.now();return t.then((...t)=>{const o=Date.now()-n;o<r?setTimeout(()=>e.resolve(...t),r-o):e.resolve(...t);}).catch(t=>{const o=Date.now()-n;o<r?setTimeout(()=>e.reject(t),r-o):e.reject(t);}),e.promise},timeout:function(t,r,e){V(t),_$1(r=r??1);const n=nt(r,e),o=Date.now();return t.then((...t)=>{Date.now()-o<=r?n.resolve(...t):n.reject(new Error(e??`Promise Timeout: ${r}ms`));}).catch(t=>{!n.resolved&&!n.rejected&&n.reject(t);}),n.promise},allSettled:ot,returnValuePromised:it,series:async function(t){D(t);const r=[];for(const e of t)if(H(e),u$1.isFunction(e))r.push(await it(e));else {if(!u$1.isPromise(e))throw new Error(`Invalid Task: ${e}`);r.push(await e);}return r},seriesAllSettled:async function(t){D(t);const r=[];for(const e of t){H(e);try{r.push({ok:!0,result:await e()});}catch(t){r.push({ok:false,result:t});}}return r},parallel:async function(t,r=5){if(D(t),_$1(r),r<=0)throw new Error(`Invalid maxParallel: ${r}, should > 0`);t.forEach(t=>H(t));const e=[];if(t.length<=r){const r=await Promise.all(t.map(t=>it(t)));return e.push(...r),e}const n=[];for(const o of t)if(H(o),n.push(o),n.length>=r){const t=await Promise.all(n.map(t=>it(t)));e.push(...t),n.length=0;}if(n.length>0&&n.length<r){const t=await Promise.all(n.map(t=>it(t)));e.push(...t);}return e},parallelAny:async function(t,r=5,n){if(D(t,"tasks"),_$1(r),0===t.length)throw new Error("Empty Tasks");if(r<=0)throw new Error(`Invalid maxParallel: ${r}, should > 0`);const o=[];let i=0,s=0;const f=nt();function a(){if(i>=t.length)return;if(s>r)return;const c=t[i++];let l;s++,l=u$1.isPromise(c)?c:u$1.isFunction(c)?it(c):Promise.reject(new TypeError(`Invalid task: ${typeof c}, Only Promise or Function allowed`)),l.then(t=>{s--,f.resolve(t);}).catch(r=>{o.push(r),o.length>=t.length&&f.pending?f.reject(new e$1(n??"All Tasks Failed",o)):(s--,a());});}const c=Math.min(t.length,r);for(let t=0;t<c;t++)a();return f.promise},parallelAllSettled:async function(t,r=5){if(D(t),_$1(r),r<=0)throw new Error(`Invalid maxParallel: ${r}, should > 0`);t.forEach(t=>H(t));const e=[];if(t.length<=r){const r=await ot(t.map(t=>it(t)));return e.push(...r),e}const n=[];for(const o of t)if(n.push(o),n.length>=r){const t=await ot(n.map(t=>it(t)));e.push(...t),n.length=0;}if(n.length>0&&n.length<r){const t=await ot(n.map(t=>it(t)));e.push(...t);}return e},wait:function(t){J(t);const r={};let e;return r.timerHandler=e=setTimeout(()=>{clearTimeout(e),r._resolve();},t),r.promise=new Promise((t,n)=>{r._resolve=r=>{null!=e&&clearTimeout(e),t(r);};}),r.wakeup=()=>{r._resolve();},r}};function nt(t=-1,r){_$1(t);const e={};let n;return e.pending=true,e.canceled=false,e.rejected=false,e.resolved=false,t>=0&&(e.timerCleared=false,e.timerHandler=n=setTimeout(()=>{clearTimeout(n),e.timerCleared=true,e.reject(new Error(r??`Promise Timeout: ${t}ms`));},t)),e.promise=new Promise((t,r)=>{e.resolve=r=>{e.resolved||e.rejected||e.canceled||(null!=n&&(clearTimeout(n),e.timerCleared=true),e.pending=false,e.canceled=false,e.rejected=false,e.resolved=true,t(r));},e.reject=t=>{e.resolved||e.rejected||e.canceled||(null!=n&&(clearTimeout(n),e.timerCleared=true),e.pending=false,e.canceled=false,e.resolved=false,e.rejected=true,r(t));};}),e.cancel=e.promise.cancel=t=>{e.resolved||e.rejected||e.canceled||(null!=n&&(clearTimeout(n),e.timerCleared=true),e.reject(t??new Error("Cancelled")),e.canceled=e.promise.canceled=true);},e}async function ot(t){D(t);const r=await Promise.allSettled(t),e=[];for(const t of r)"fulfilled"===t.status&&e.push({ok:true,result:t.value}),"rejected"===t.status&&e.push({ok:false,result:t.reason});return e}function it(t){try{const r=t();return u$1.isPromise(r)?r:Promise.resolve(r)}catch(t){return Promise.reject(t)}}new TextDecoder;new TextEncoder;
// module vars
const DefaultMinInterval = 50;
const DefaultMaxInterval = 30 * 1000; // 30s
const DefaultMaxRetries = 3;
// internal
// module vars
const { assertPositive: assertPositive$5, assertNotNegative: assertNotNegative$3 } = M;
const { isNumber } = u$1;
class Policy {
/**
* Creates a new Policy instance with specified retry bounds.
*/
constructor () {
this._min = DefaultMinInterval;
this._max = DefaultMaxInterval;
this._nextInterval = this._min;
this._jitter = 0;
}
get jitter () {
return this._jitter
}
set jitter (jitter) {
assertNotNegative$3(jitter, 'jitter');
this._jitter = jitter;
}
/**
* Copies settings to target policy.
* 1. range
* 2. nextInterval value
* @param {Policy} targetPolicy - The policy to modify.
*/
copyPolicySettingTo (targetPolicy) {
targetPolicy.range(this._min, this._max);
targetPolicy._nextInterval = this._nextInterval;
}
/**
* Sets a fixed interval retry policy.
}
/**
* Sets the minimum and maximum intervals for retries.
* @param {number} min - Minimum delay in milliseconds (must be positive and less than max)
* @param {number} max - Maximum delay in milliseconds (must be positive and greater than min)
* @returns {this} Returns the Retrier instance for chaining
* @throws {Error} If min is not less than max or if values are not positive
*/
range (min, max) {
assertPositive$5(min, 'min');
assertPositive$5(max, 'max');
if (min >= max) {
throw new Error('min must < max')
}
this._min = min;
if (this._nextInterval < this._min) {
this._nextInterval = this._min;
}
this._max = max;
if (this._nextInterval > this._max) {
this._nextInterval = this._max;
}
return this
}
/**
* Sets the minimum retry delay in milliseconds.
* 1. will change currentInterval to min
* @param {number} min - The minimum delay (must be positive and less than max).
* @returns {this} The retrier instance for chaining.
* @throws {Error} If min is not positive or is greater than/equal to max.
*/
min (min) {
assertPositive$5(min, 'min');
if (min >= this._max) {
throw new Error('min must < max')
}
this._min = min;
if (this._nextInterval < this._min) {
this._nextInterval = this._min;
}
return this
}
/**
* Sets the maximum retry retry delay in milliseconds.
* @param {number} max - The maximum delay (must be positive and greater than min).
* @throws {Error} If max is not greater than min.
* @returns {this} The retrier instance for chaining.
*/
max (max) {
assertPositive$5(max, 'max');
if (max <= this._min) {
throw new Error('max must > min')
}
this._max = max;
if (this._nextInterval > this._max) {
this._nextInterval = this._max;
}
return this
}
reset () {
this._nextInterval = this._min;
return this
}
/**
* Interval ms of next execution
* @param {number} retries current retry times
* @returns {number}
*/
generate (retries) {
const rtnVal = this._nextInterval;
this._increase(retries);
return rtnVal
}
/**
* @param {number} retries current retry times
* @returns {number}
*/
_increase (retries) {
const generated = this._next(retries);
if (!isNumber(generated)) {
throw new Error('Generated Next Interval Not Number')
}
const nextInterval = this._jitter <= 0 ? generated : generated + Math.floor(Math.random() * this._jitter);
if (nextInterval < this._min) {
return (this._nextInterval = this._min)
} else if (nextInterval > this._max) {
return (this._nextInterval = this._max)
}
return (this._nextInterval = nextInterval)
}
/**
* subclass should implement this method
* @param {number} retries current retry times
* @returns {number} The interval in milliseconds to wait before the next retry attempt.
* @protected
*/
_next (retries) {
throw new Error('Not Impled Yet')
}
}
var e={isFunction:t,isNil:n};function t(e){return "function"==typeof e}function n(e){return null==e}function s(e){return null!=e&&"string"==typeof e}var r={assertNumber:function(e,t){if(!function(e){return null!=e&&"number"==typeof e}(e))throw new Error(`${t?'"'+t+'" ':""}Not Number: type=${typeof e} value=${i(e)}`)},assertFunction:function(e,n){if(!t(e))throw new Error(`${n?'"'+n+'" ':""}Not Function: type=${typeof e} value=${i(e)}`)},assertNotNil:function(e,t){if(n(e))throw new Error((t?'"'+t+'" ':"")+"Should Not Nil")},assertString:function(e,t){if(!s(e))throw new Error(`${t?'"'+t+'" ':""}Not String: type=${typeof e} value=${i(e)}`)},assertStringOrSymbol:function(e,t){if(!s(e)&&!function(e){return null!=e&&"symbol"==typeof e}(e))throw new Error(`${t?'"'+t+'" ':""}Not String or Symbol: type=${typeof e} value=${i(e)}`)}};function i(e){if(null===e)return "null";if(void 0===e)return "undefined";const t=typeof e;if("string"===t)return e;if("symbol"===t)return `Symbol(${e.description})`;if("function"===t)return `Function ${e.name}(){}`;if(e instanceof String)return e.toString();if(Number.isNaN(e))return "NaN";if(e===1/0)return "Infinity";if(e===-1/0)return "-Infinity";if(e instanceof Error)return `${e.constructor.name}: ${e.message}`;if(e instanceof Promise)return "Promise";if(e instanceof Set)return `Set: ${i(Array.from(e))}`;if(e instanceof Map)return `Map: ${i(Array.from(e.entries()))}`;if(e instanceof RegExp)return e.toString();if(Array.isArray(e))return `[${e.map(i).join(", ")}]`;let n;try{n=JSON.stringify(e);}catch(t){n=e.toString();}return n}new TextDecoder,new TextEncoder;const l="DOwner$#$",{assertFunction:a,assertNotNil:o}=r;class c{constructor(e,t,n=false){o(e,"event"),a(t,"callback"),this._event=e,this._callback=t,this._isOnce=!!n,this._owner=void 0;}set owner(e){this._owner=e;}get owner(){return this._owner===l?void 0:this._owner}get event(){return this._event}get isOnce(){return this._isOnce}isSameCallback(e){return this._callback===e}get callback(){return this._callback}invoke(...e){try{return this._callback(...e)}finally{if(this._isOnce)try{this._event._remove(this);}catch(e){console.warn(e);}}}listener(...e){return this.invoke(...e)}}const{isFunction:h,isNil:u}=e,{assertStringOrSymbol:_,assertFunction:f}=r;class m{static get DefaultOwner(){return l}constructor(e){_(e,"eventName"),this._name=e,this._callbacks=new Set,this._listeners=[],this._callback2Listeners=new Map,this._listener2Owner=new Map,this._owner2Listeners=new Map;}get name(){return this._name}isEmpty(){return 0===this._callbacks.size}rawListeners(){return [...this._listeners]}listenerCount(e){return null==e?this._listeners.length:this._callback2Listeners.get(e)?.size??0}callbacks(){return [...this.rawListeners().map(e=>e.callback)]}emit(...e){if(0===this._listeners.length)return false;for(const t of [...this._listeners])t.invoke(...e);return true}emitOwner(e,...t){if(0===this._listeners.length)return false;const n=this._owner2Listeners.get(e);if(null==n)return false;for(const e of [...n])e.invoke(...t);return true}hasListener(e){return !!h(e)&&this._callbacks.has(e)}hasOwner(e){return !u(e)&&this._owner2Listeners.has(e)}addListener(e,t){return this._addListener(e,t,false,false)}prependListener(e,t){return this._addListener(e,t,false,true)}addOnceListener(e,t){return this._addListener(e,t,true,false)}prependOnceListener(e,t){return this._addListener(e,t,true,true)}_addListener(e,t,n,s){if(u(e))return false;f(e),this._callbacks.has(e)||this._callbacks.add(e),t=t??l;const r=new c(this,e,n);r.owner=t,s?this._listeners.unshift(r):this._listeners.push(r),this._listener2Owner.set(r,t);let i=this._callback2Listeners.get(e);null==i&&(i=new Set,this._callback2Listeners.set(e,i)),i.add(r);let a=this._owner2Listeners.get(t);return null==a&&(a=new Set,this._owner2Listeners.set(t,a)),a.add(r),true}removeListener(e){if(u(e))return false;if(!this._callbacks.has(e))return false;this._callbacks.delete(e);const t=this._callback2Listeners.get(e);if(null==t)return false;this._callback2Listeners.delete(e);for(const e of t){ -1!==this._listeners.indexOf(e)&&this._listeners.splice(this._listeners.indexOf(e),1);const t=this._listener2Owner.get(e);if(null==t)continue;this._listener2Owner.delete(e);const n=this._owner2Listeners.get(t);null!=n&&(n.delete(e),0===n.size&&this._owner2Listeners.delete(t));}return true}_remove(e){const t=this._listeners.indexOf(e);-1!==t&&this._listeners.splice(t,1);const{callback:n}=e,s=this._callback2Listeners.get(n);null!=s&&(s.delete(e),0===s.size&&(this._callback2Listeners.delete(n),this._callbacks.delete(n)));const r=this._listener2Owner.get(e);if(null==r)return;this._listener2Owner.delete(e);const i=this._owner2Listeners.get(r);null!=i&&(i.delete(e),0===i.size&&this._owner2Listeners.delete(r));}removeAllListeners(e){if(u(e))return this._callbacks.clear(),this._listeners.length=0,this._callback2Listeners.clear(),this._listener2Owner.clear(),this._owner2Listeners.clear(),this;const t=this._owner2Listeners.get(e);if(null==t)return this;this._owner2Listeners.delete(e);for(const e of t){ -1!==this._listeners.indexOf(e)&&this._listeners.splice(this._listeners.indexOf(e),1),this._listener2Owner.delete(e);const{callback:t}=e,n=this._callback2Listeners.get(t);null!=n&&(n.delete(e),0===n.size&&(this._callback2Listeners.delete(t),this._callbacks.delete(t)));}return this}}const{isNil:d}=e,{assertString:w,assertFunction:L,assertNumber:v,assertStringOrSymbol:g,assertNotNil:p}=r,b=["on","once","addListener","prependListener","prependOnceListener","off","offAll","offOwner","removeAllListeners","removeListener","emit","emitOwner","setMaxListeners","getMaxListeners","hasOwner","listeners","listenerCount","eventNames","rawListeners"];let O=10;class k{static mixin(e){const t=new k;e.__emitter=t;for(const n of b){const s=t[n];e[n]=s.bind(t);}return e}static get defaultMaxListeners(){return O}static set defaultMaxListeners(e){v(e),O=e??10;}constructor(){this._name2Event=new Map,this._maxListeners=O;}clean(){this._name2Event.clear();}addListener(e,t,n){return this.on(e,t,n)}prependListener(e,t,n){w(e),L(t),this._checkMaxListeners(e);return this._getOrCreateEvent(e).prependListener(t,n),this}prependOnceListener(e,t,n){w(e),L(t),this._checkMaxListeners(e);return this._getOrCreateEvent(e).prependOnceListener(t,n),this}emit(e,...t){const n=this._name2Event.get(e);return null!=n&&!n.isEmpty()&&(n.emit(...t),true)}emitOwner(e,t,...n){if(null==t)throw new Error('Missing "owner"');const s=this._name2Event.get(e);return null!=s&&!s.isEmpty()&&(s.emitOwner(t,...n),true)}eventNames(){return [...this._name2Event.keys()]}getMaxListeners(){return this._maxListeners}listenerCount(e,t){g(e,"eventName");const n=this._name2Event.get(e);return null==n||n.isEmpty()?0:n.listenerCount(t)}listeners(e){g(e,"eventName");const t=this._name2Event.get(e);return null==t||t.isEmpty()?[]:t.callbacks()}off(e,t){const n=this._name2Event.get(e);return null==n?this:(n.removeListener(t),n.isEmpty()?(this._name2Event.delete(e),this):this)}offAll(e,t){g(e,"eventName");const n=this._name2Event.get(e);return null==n?this:(n.removeAllListeners(t),n.isEmpty()?(this._name2Event.delete(e),this):this)}offOwner(e){p(e,"owner");const t=[...this._name2Event.values()];for(const n of t)n.removeAllListeners(e),n.isEmpty()&&this._name2Event.delete(n.name);return this}on(e,t,n){w(e),L(t),this._checkMaxListeners(e);return this._getOrCreateEvent(e).addListener(t,n),this}_checkMaxListeners(e){let t=0;0!==this._maxListeners&&this._maxListeners!==1/0&&(t=this.listenerCount(e))>=this._maxListeners&&console.warn(`maxlistenersexceededwarning: Possible EventEmitter memory leak detected. ${t} ${e} listeners added to [${this}]. Use emitter.setMaxListeners() to increase limit`);}once(e,t,n){w(e),L(t);return this._getOrCreateEvent(e).addOnceListener(t,n),this}rawListeners(e){return this._name2Event.get(e)?.rawListeners()||[]}removeAllListeners(e,t){return this.offAll(e,t)}removeListener(e,t){return this.off(e,t)}setMaxListeners(e){if(v(e),e<0)throw new RangeError("maxListeners must >=0");return this._maxListeners=e,this}_getOrCreateEvent(e){if(this._name2Event.has(e))return this._name2Event.get(e);const t=new m(e);return this._name2Event.set(e,t),t}hasOwner(e){if(d(e))return false;for(const t of this._name2Event.values())if(t.hasOwner(e))return true;return false}}
const Start = 'start'; // retry started
const Stop = 'stop'; // retry stopped
const Retry = 'retry'; // one retry began
const Success = 'success'; // one task running succeeded
const Failure = 'failure'; // one task ran failed
const Timeout = 'timeout'; // total timeout
const TaskTimeout = 'task-timeout'; // one task timed out
const Completed = 'complete'; // all retries completed
const MaxRetries = 'max-retries'; // Reach the max retries
var Event = {
Start,
Retry,
Success,
Failure,
Timeout,
TaskTimeout,
Stop,
Completed,
MaxRetries
};
// 3rd
// internal
// module vars
const { assertPositive: assertPositive$4 } = M;
class FixedIntervalPolicy extends Policy {
/**
* Creates a fixed interval retry policy with the specified interval.
* @param {number} interval - The fixed interval (in milliseconds) between retry attempts.
*/
constructor (interval) {
super();
this.interval = interval;
}
/**
* @param {number} interval - The fixed interval (in milliseconds) between retry attempts.
*/
set interval (interval) {
assertPositive$4(interval, 'interval');
this._interval = interval;
this._nextInterval = interval;
this._min = interval;
this._max = interval;
}
/**
* @returns {number}
*/
get interval () {
// @ts-ignore not possible be undefined
return this._interval
}
/**
* Interval ms of next execution
* @param {number} retries
* @returns {number}
* @throws {Error} Always throws "Not Implemented Yet" error.
*/
_next (retries) {
return this.interval
}
}
// 3rd
// internal
// module vars
const { assertPositive: assertPositive$3 } = M;
class FixedIncreasePolicy extends Policy {
/**
* each call to _next() increases the interval by "increasement".
* @param {number} increasement - The fixed interval (in milliseconds) between retry attempts.
*/
constructor (increasement) {
super();
assertPositive$3(increasement, 'increasement');
this._increasement = increasement;
}
set increasement (increasement) {
assertPositive$3(increasement, 'increasement');
this._increasement = increasement;
}
get increasement () {
return this._increasement
}
/**
* Interval ms of next execution
* @param {number} retries
* @returns {number}
*/
_next (retries) {
if (this._nextInterval >= this._max) {
return this._max
}
return this._nextInterval + this.increasement
}
}
// 3rd
// internal
// module vars
const { assertPositive: assertPositive$2 } = M;
class FactoreIncreasePolicy extends Policy {
/**
* each call to _next() increases the interval by lastInterval * factor
* @param {number} factor - the increasement factor, >= 1
*/
constructor (factor) {
super();
assertPositive$2(factor, 'factor');
if (factor < 1) {
throw new Error('factor must be >= 1')
}
this._factor = factor;
}
set factor (factor) {
assertPositive$2(factor, 'factor');
if (factor < 1) {
throw new Error('factor must be >= 1')
}
this._factor = factor;
}
get factor () {
return this._factor
}
/**
* Interval ms of next execution
* @param {number} retries
* @returns {number}
*/
_next (retries) {
if (this._nextInterval >= this._max) {
return this._max
}
return this._nextInterval * this.factor
}
}
// 3rd
// internal
// module vars
const { assertPositive: assertPositive$1 } = M;
class ShuttlePolicy extends Policy {
/**
* the inteval value shuttles between min and max
* @param {number} stepLength - the step length to change
*/
constructor (stepLength) {
super();
assertPositive$1(stepLength, 'stepLength');
this._stepLength = stepLength;
this.increasement = stepLength;
}
set stepLength (stepLength) {
assertPositive$1(stepLength, 'stepLength');
this._stepLength = stepLength;
this.increasement = stepLength;
}
get stepLength () {
return this._stepLength
}
/**
* Interval ms of next execution
* @param {number} retries
* @returns {number}
* @throws {Error} Always throws "Not Implemented Yet" error.
*/
_next (retries) {
const nextInterval = this._nextInterval + this.increasement;
if (nextInterval >= this._max) {
this.increasement = -this.stepLength;
return this._max
} else if (nextInterval <= this._min) {
this.increasement = this.stepLength;
return this._min
}
return nextInterval
}
}
// internal
// module vars
const { assertNotNegative: assertNotNegative$2 } = M;
/**
* @class FixedBackoff
*/
class FixedBackoff extends FixedIntervalPolicy {
/**
* Creates a fixed backoff policy with optional jitter.
* @param {number} fixedInterval - The fixed interval between retries in milliseconds.
* @param {number} [jitter=500] - The maximum random jitter to add to the interval in milliseconds.
*/
constructor (fixedInterval, jitter = 500) {
super(fixedInterval);
assertNotNegative$2(jitter, 'jitter');
this._jitter = jitter ?? 500;
}
}
// internal
// module vars
const { assertNotNegative: assertNotNegative$1 } = M;
/**
* @class ExponentialBackoffPolicy
*/
class ExponentialBackoffPolicy extends FactoreIncreasePolicy {
/**
* Creates an exponential backoff policy with optional jitter.
* @param {number} [jitter=500] - Maximum jitter in milliseconds to add to backoff intervals.
*/
constructor (jitter = 500) {
super(2);
assertNotNegative$1(jitter, 'jitter');
this._jitter = jitter ?? 500;
}
}
// internal
// module vars
const { assertNotNegative } = M;
/**
* @class LinearBackoff
*/
class LinearBackoff extends FixedIncreasePolicy {
/**
* Creates a linear backoff policy with optional jitter.
* @param {number} increasement - The base increasement value for backoff.
* @param {number} [jitter=500] - The maximum jitter value to add to backoff (default: 500).
*/
constructor (increasement, jitter = 500) {
super(increasement);
assertNotNegative(jitter, 'jitter');
this._jitter = jitter ?? 500;
}
}
// owned
/**
* @typedef {import('./retrier.js').default} Retrier
*/
// module vars
const { assertNotNil, assertFunction: assertFunction$1 } = M;
class Task {
/**
* Creates a new Task instance.
* @param {Retrier} retrier - The retrier instance.
* @param {Function} task - The function to be executed as the task.
*/
constructor (retrier, task) {
assertNotNil(retrier, 'retrier');
assertFunction$1(task, 'task');
this.retrier = retrier;
this.task = task;
this.result = undefined;
/** @type {Error|undefined} */
this.error = undefined;
}
get failed () {
return this.error != null
}
get succeeded () {
return this.error == null
}
/**
* Executes the task with the given retry parameters.
* 1. if execution throw error, keep error in this.error
* 2. if execution return value, keep it in this.result
* 3. always return Promise<void>
* @param {number} retries - The number of retries attempted so far.
* @param {number} latence - The current latency ms.
* @param {number} nextInterval - The next interval ms.
* @returns {Promise<void>} The result of the task execution.
*/
async execute (retries, latence, nextInterval) {
try {
this.result = await this.task(retries, latence, nextInterval);
this.error = undefined;
} catch (e) {
this.error = e instanceof Error ? e : new Error(String(e));
}
}
dispose () {
// @ts-ignore
this.retrier = undefined;
}
}
// owned
/**
* @typedef {import('./retrier.js').default} Retrier
*/
class AlwaysTask extends Task {
/**
* Checks if the given task is an instance of AlwaysTask.
* @param {*} task - The task to check.
* @returns {boolean} True if the task is an instance of AlwaysTask, false otherwise.
*/
static isAlwaysTask (task) {
return task instanceof AlwaysTask
}
/**
* Creates an AlwaysTask instance.
* @param {Retrier} retrier - The retrier instance to use for retry logic
* @param {Function} task - The task function to execute
* @param {boolean} resetRetryPolicyAfterSuccess - Whether to reset retry policy after successful execution
*/
constructor (retrier, task, resetRetryPolicyAfterSuccess) {
super(retrier, task);
this.resetPolicy = resetRetryPolicyAfterSuccess;
}
/**
* Executes the task with the given retry parameters.
* @param {number} retries - The number of retries attempted so far.
* @param {number} latence - The current latency ms.
* @param {number} nextInterval - The next interval ms.
* @returns {Promise<*>} The result of the task execution.
*/
async execute (retries, latence, nextInterval) {
await super.execute(retries, latence, nextInterval);
if (this.succeeded && this.resetPolicy) {
this.retrier.resetRetryPolicy();
}
}
}
// owned
/**
* @typedef {import('./retrier.js').default} Retrier
*/
/**
* The task will execute forever despite of failure or success.
* 1. Ignore Retrier total timeout setting
* 2. Ignore Retrier max retries setting
*/
class ForeverTask extends Task {
/**
* Checks if the given task is an instance of ForeverTask.
* @param {*} task - The task to check.
* @returns {boolean} True if the task is an instance of ForeverTask, false otherwise.
*/
static isForeverTask (task) {
return task instanceof ForeverTask
}
/**
* Creates an ForeverTask instance.
* @param {Retrier} retrier - The retrier instance to use for retry logic
* @param {Function} task - The task function to execute
* @param {boolean} resetRetryPolicyAfterSuccess - Whether to reset retry policy after successful execution
*/
constructor (retrier, task, resetRetryPolicyAfterSuccess) {
super(retrier, task);
this.resetPolicy = resetRetryPolicyAfterSuccess;
}
/**
* Executes the task with the given retry parameters.
* @param {number} retries - The number of retries attempted so far.
* @param {number} latence - The current latency ms.
* @param {number} nextInterval - The next interval ms.
* @returns {Promise<*>} The result of the task execution.
*/
async execute (retries, latence, nextInterval) {
await super.execute(retries, latence, nextInterval);
if (this.succeeded && this.resetPolicy) {
this.retrier.resetRetryPolicy();
}
}
}
// internal
/**
* @typedef {import('./policy.js').default} Policy
* @typedef {import('@creejs/commons-lang/types/promise-utils').Deferred} Deferred
*/
// module vars
const { assertPositive, assertString, assertFunction, assertNumber } = M;
const { isNil } = u$1;
const TaskTimoutFlag = '!#@%$&^*';
/**
* @extends EventEmitter
*/
class Retrier {
/**
* Creates a new Retrier instance with the specified name.
* @param {string} name - The name to assign to the retrier.
* @returns {Retrier} A new Retrier instance with the given name.
*/
static naming (name) {
const retrier = new Retrier();
retrier.name(name);
return retrier
}
/**
* Creates and returns a Retrier instance configured for infinite retries.
* @returns {Retrier} A Retrier instance with infinite retry behavior.
*/
static infinite () {
const retrier = new Retrier();
retrier.infinite();
return retrier
}
/**
* Creates a retrier configured to attempt an operation a specified number of times.
* @param {number} times - The maximum number of retry attempts.
* @returns {Retrier} A configured Retrier instance with the specified max retries.
*/
static times (times) {
const retrier = new Retrier();
retrier.times(times);
return retrier
}
/**
* Alias for times.
* @param {number} maxRetries - The maximum number of retry attempts.
* @returns {Retrier} A configured Retrier instance with the specified max retries.
*/
static maxRetries (maxRetries) {
const retrier = new Retrier();
retrier.maxRetries(maxRetries);
return retrier
}
/**
* Sets the minimum Interval for the retrier and returns the instance.
* @param {number} min - The minimum Interval in milliseconds.
* @returns {Retrier} The retrier instance with updated minimum Interval.
*/
static min (min) {
const retrier = new Retrier();
retrier.min(min);
return retrier
}
/**
* Sets the maximum Interval for the retrier and returns the instance.
* @param {number} max - The maximum Interval in milliseconds.
* @returns {Retrier} The retrier instance with updated maximum Interval.
*/
static max (max) {
const retrier = new Retrier();
retrier.max(max);
return retrier
}
/**
* Creates a retrier with the specified Interval range.
* @param {number} min - Minimum Interval.
* @param {number} max - Maximum Interval.
* @returns {Retrier} A new Retrier instance configured with specified Interval range.
*/
static range (min, max) {
const retrier = new Retrier();
retrier.range(min, max);
return retrier
}
/**
* Creates a retrier with a fixed interval between attempts.
* @param {number} fixedInterval - The fixed interval in milliseconds between retry attempts.
* @returns {Retrier} A new Retrier instance configured with the specified fixed interval.
*/
static fixedInterval (fixedInterval) {
const retrier = new Retrier();
retrier.fixedInterval(fixedInterval);
return retrier
}
/**
* Creates a retrier with a fixed backoff strategy.
* @param {number} fixedInterval - The fixed interval between retries in milliseconds.
* @param {number} [jitter=500] - The maximum jitter to add to the interval in milliseconds.
* @returns {Retrier} A retrier instance configured with fixed backoff.
*/
static fixedBackoff (fixedInterval, jitter = 500) {
const retrier = new Retrier();
retrier.fixedBackoff(fixedInterval, jitter);
return retrier
}
/**
* Creates a retrier with a fixed increase strategy.
* @param {number} increasement - The fixed amount to increase on each retry.
* @returns {Retrier} A retrier instance configured with fixed increase.
*/
static fixedIncrease (increasement) {
const retrier = new Retrier();
retrier.fixedIncrease(increasement);
return retrier
}
/**
* Creates a retrier with a fixed increase strategy.
* @param {number} increasement - The fixed amount to increase on each retry.
* @param {number} [jitter=500] - The maximum jitter to add to the interval in milliseconds.
* @returns {Retrier} A retrier instance configured with fixed increase.
*/
static linearBackoff (increasement, jitter = 500) {
const retrier = new Retrier();
retrier.linearBackoff(increasement, jitter);
return retrier
}
/**
* Creates a new Retrier instance with factor-increase strategy.
* @param {number} factor - The factor by which to increase the interval on each retry.
* @returns {Retrier} A new Retrier instance with factor-increase strategy.
*/
static factorIncrease (factor) {
const retrier = new Retrier();
retrier.factorIncrease(factor);
return retrier
}
/**
* Creates a new Retrier instance with exponential-backoff strategy.
* @param {number} [jitter=500]
* @returns {Retrier} A new Retrier instance
*/
static exponentialBackoff (jitter = 500) {
const retrier = new Retrier();
retrier.exponentialBackoff(jitter);
return retrier
}
/**
* Creates a Retrier instance with a shuttle-interval strategt.
* @param {number} stepLength - The interval step length of each change
* @returns {Retrier} A configured Retrier instance with shuttle-interval strategt.
*/
static shuttleInterval (stepLength) {
const retrier = new Retrier();
retrier.shuttleInterval(stepLength);
return retrier
}
/**
* Creates a Retrier instance with a total-opertion timeout.
* @param {number} timeout - The timeout value in milliseconds.
* @returns {Retrier} A Retrier instance configured with the given timeout.
*/
static timeout (timeout) {
const retrier = new Retrier();
retrier.timeout(timeout);
return retrier
}
/**
* Creates a retrier instance with a single-task timeout.
* @param {number} timeout - The timeout duration in milliseconds for the retrier task.
* @returns {Retrier} A Retrier instance configured with the given task timeout.
*/
static taskTimeout (timeout) {
const retrier = new Retrier();
retrier.taskTimeout(timeout);
return retrier
}
/**
* Creates a new Retrier instance, sets the task to be retried, and starts the retry process.
* @param {Function} task - The asynchronous task static to be retried.
* @returns {Promise<*>} A promise that resolves when the retry process completes.
*/
static start (task) {
const retrier = new Retrier();
retrier.task(task);
return retrier.start()
}
/**
* Creates a new Retrier instance with a fixed interval policy.
* @param {number} [fixedInterval=1000] - The fixed interval in milliseconds between retry attempts. Defaults to 1000ms if not provided.
*/
constructor (fixedInterval) {
k.mixin(this);
/**
* @type {Policy}
*/
this._policy = new FixedIntervalPolicy(fixedInterval ?? 1000);
this._maxRetries = DefaultMaxRetries;
this._currentRetries = 1;
/**
* Timetou for total operation
* @type {number}
*/
this._timeout = 120000; // 120s
/**
* Timetou for single task
*/
this._taskTimeout = 2000; // 20s
this._name = 'unamed'; // Retrier name
/**
* A Deferred Object as Singal to prevent Task concurrent start
* @type {Deferred|undefined}
*/
this._taskingFlag = undefined;
/**
* A Deferred Object as Singal to prevent Task concurrent stop
* @type {Deferred|undefined}
*/
this._breakFlag = undefined;
/**
* Reason for break
* @type {Error|undefined}
*/
this._breakReason = undefined;
}
get running () {
return !isNil(this._taskingFlag)
}
/**
* Sets the name of the retrier.
* @param {string} retrierName - The name to assign to the retrier.
* @returns {this} The retrier instance for chaining.
*/
name (retrierName) {
assertString(retrierName, 'name');
this._assertChangeable(retrierName);
this._name = retrierName;
return this
}
/**
* Sets the retry attempts to be infinite by setting max retries to maximum safe integer.
* @returns {this} The retrier instance for chaining.
*/
infinite () {
this._assertChangeable('infinite');
this._maxRetries = Infinity;
return this
}
/**
* Sets the maximum number of retry attempts.
* @param {number} times - The maximum number of retries.
* @returns {this} The Retrier instance for chaining.
*/
times (times) {
this._assertChangeable('times');
return this.maxRetries(times)
}
/**
* Sets the maximum number of retry attempts.
* @param {number} maxRetries - The maximum number of retries (must be positive).
* @returns {this} The retrier instance for chaining.
*/
maxRetries (maxRetries) {
assertPositive(maxRetries, 'maxRetries');
this._assertChangeable('maxRetries');
this._maxRetries = maxRetries;
return this
}
/**
* Sets the minimum retry delay in milliseconds.
* @param {number} min - The minimum delay (must be positive and less than max).
* @returns {this} The retrier instance for chaining.
* @throws {Error} If min is not positive or is greater than/equal to max.
*/
min (min) {
this._assertChangeable('min');
this._policy.min(min);
return this
}
/**
* Sets the maximum retry retry delay in milliseconds.
* @param {number} max - The maximum delay (must be positive and greater than min).
* @throws {Error} If max is not greater than min.
* @returns {this} The retrier instance for chaining.
*/
max (max) {
this._assertChangeable('max');
this._policy.max(max);
return this
}
/**
* Sets the minimum and maximum intervals for retries.
* @param {number} min - Minimum delay in milliseconds (must be positive and less than max)
* @param {number} max - Maximum delay in milliseconds (must be positive and greater than min)
* @returns {Retrier} Returns the Retrier instance for chaining
* @throws {Error} If min is not less than max or if values are not positive
*/
range (min, max) {
this._assertChangeable('range');
this._policy.range(min, max);
return this
}
/**
* Sets a fixed interval retry policy.
* @param {number} fixedInterval - The fixed interval in milliseconds between retries.
* @returns {Retrier} The Retrier instance for chaining.
*/
fixedInterval (fixedInterval) {
this._assertChangeable('fixedInterval');
const oldPolicy = this._policy;
if (oldPolicy instanceof FixedIntervalPolicy) {
oldPolicy.interval = fixedInterval;
return this
}
const newPolicy = new FixedIntervalPolicy(fixedInterval);
oldPolicy?.copyPolicySettingTo(newPolicy);
newPolicy.reset();
this._policy = newPolicy;
return this
}
/**
* sets a fixed backoff strategy.
* @param {number} fixedInterval - The fixed interval between retries in milliseconds.
* @param {number} [jitter=500] - The maximum jitter to add to the interval in milliseconds.
* @returns {Retrier} A retrier instance configured with fixed backoff.
*/
fixedBackoff (fixedInterval, jitter = 500) {
this._assertChangeable('fixedBackoff');
const oldPolicy = this._policy;
if (oldPolicy instanceof FixedIntervalPolicy) {
oldPolicy.interval = fixedInterval;
oldPolicy.jitter = jitter;
return this
}
const newPolicy = new FixedBackoff(fixedInterval, jitter);
oldPolicy?.copyPolicySettingTo(newPolicy);
newPolicy.reset();
this._policy = newPolicy;
return this
}
/**
* Sets a fixed increase policy for retry intervals.
* @param {number} increasement - The fixed amount to increase the interval by on each retry.
* @returns {this} The retrier instance for chaining.
*/
fixedIncrease (increasement) {
this._assertChangeable('fixedIncrease');
const oldPolicy = this._policy;
if (oldPolicy instanceof FixedIncreasePolicy) {
oldPolicy.increasement = increasement;
return this
}
const newPolicy = new FixedIncreasePolicy(increasement);
oldPolicy?.copyPolicySettingTo(newPolicy);
newPolicy.reset();
this._policy = newPolicy;
return this
}
/**
* Sets a fixed increase policy for retry intervals.
* @param {number} increasement - The fixed amount to increase the interval by on each retry.
* @param {number} [jitter=500] - The maximum jitter to add to the interval in milliseconds.
* @returns {this} The retrier instance for chaining.
*/
linearBackoff (increasement, jitter = 500) {
this._assertChangeable('linearBackoff');
const oldPolicy = this._policy;
if (oldPolicy instanceof LinearBackoff) {
oldPolicy.increasement = increasement;
oldPolicy.jitter = jitter;
return this
}
const newPolicy = new LinearBackoff(increasement, jitter);
oldPolicy?.copyPolicySettingTo(newPolicy);
newPolicy.reset();
this._policy = newPolicy;
return this
}
/**
* Sets a fixed increase factor for retry delays.
* @param {number} factor - The multiplier for delay increase between retries.
* @returns {this} The retrier instance for method chaining.
*/
factorIncrease (factor) {
this._assertChangeable('factorIncrease');
const oldPolicy = this._policy;
if (oldPolicy instanceof FactoreIncreasePolicy) {
oldPolicy.factor = factor;
return this
}
const newPolicy = new FactoreIncreasePolicy(factor);
oldPolicy?.copyPolicySettingTo(newPolicy);
newPolicy.reset();
this._policy = newPolicy;
return this
}
/**
* Creates a new Retrier instance with exponential-backoff strategy.
* @param {number} [jitter]
* @returns {Retrier} A new Retrier instance
*/
exponentialBackoff (jitter = 500) {
this._assertChangeable('exponentialBackoff');
const oldPolicy = this._policy;
if (oldPolicy instanceof ExponentialBackoffPolicy) {
oldPolicy.jitter = jitter;
return this
}
const newPolicy = new ExponentialBackoffPolicy(jitter);
oldPolicy?.copyPolicySettingTo(newPolicy);
newPolicy.reset();
this._policy = newPolicy;
return this
}
/**
* Sets a shuttle retry policy with the given step length.
* @param {number} stepLength - The interval between retry attempts.
* @returns {this} The Retrier instance for chaining.
*/
shuttleInterval (stepLength) {
this._assertChangeable('shuttleInterval');
const oldPolicy = this._policy;
if (oldPolicy instanceof ShuttlePolicy) {
oldPolicy.stepLength = stepLength;
return this
}
const newPolicy = new ShuttlePolicy(stepLength);
oldPolicy?.copyPolicySettingTo(newPolicy);
newPolicy.reset();
this._policy = newPolicy;
return this
}
/**
* Sets the timeout duration for each Task execution.
* 1. must > 0
* @param {number} timeout - The timeout durat