UNPKG

@creejs/commons-retrier

Version:
1,330 lines (1,209 loc) 48.7 kB
'use strict'; Object.defineProperty(exports, '__esModule', { value: true }); var t$1={constructorName:function(t){return t?.constructor?.name},defaults:function(t,...e){if(null==t)throw new TypeError('"target" must not be null or undefined');for(const n of e)if(null!=n)for(const e in n) void 0===t[e]&&(t[e]=n[e]);return t},extend:e$1,extends:e$1,equals:function(t,e){if(t===e)return true;if("function"==typeof t?.equals)return t.equals(e);if("function"==typeof e?.equals)return e.equals(t);return false},isBrowser:n$1,isNode:function(){return !n$1()}};function e$1(t,...e){if(null==t)throw new TypeError('"target" must not be null or undefined');for(const n of e)if(null!=n)for(const e in n)t[e]=n[e];return t}function n$1(){return "undefined"!=typeof window&&"undefined"!=typeof document}var r$1={isNumber:h$1,isNil:f$1};function s$1(t){return "function"==typeof t}function f$1(t){return null==t}function l$1(t){return !!h$1(t)&&t>0}function c$1(t){return !!h$1(t)&&t>=0}function h$1(t){return null!=t&&"number"==typeof t}function d$1(t){return null!=t&&"function"==typeof t.then}function m$1(t){return null!=t&&"string"==typeof t}var b$1={assertNumber:v$1,assertPositive:function(t,e){if(!l$1(t))throw new Error(`${e?'"'+e+'" ':" "}Not Positive: ${t}`)},assertNotNegative:function(t,e){if(!c$1(t))throw new Error(`${e?'"'+e+'" ':" "}Not "0 or Positive": ${t}`)},assertFunction:S,assertNotNil:function(t,e){if(f$1(t))throw new Error((e?'"'+e+'" ':" ")+"Should Not Nil")},assertString:O$1};function $(t,e){if(!Array.isArray(t))throw new Error(`${e?e+"":" "}Not Array: type=${typeof t} value=${JSON.stringify(t)}`)}function O$1(t,e){if(!m$1(t))throw new Error(`${e?'"'+e+'" ':" "}Not String: type=${typeof t} value=${JSON.stringify(t)}`)}function v$1(t,e){if(!h$1(t))throw new Error(`${e?'"'+e+'" ':" "}Not Number: type=${typeof t} value=${JSON.stringify(t)}`)}function S(t,e){if(!s$1(t))throw new Error(`${e?'"'+e+'" ':" "}Not Function: type=${typeof t} value=${JSON.stringify(t)}`)}function P(t,e){if(!d$1(t))throw new Error(`${e?'"'+e+'" ':" "}Not Promise: type=${typeof t} value=${JSON.stringify(t)}`)}var B={defer:U,delay:function(t,e){h$1(t)?(e=t,t=Promise.resolve()):null==t&&null==e&&(e=1,t=Promise.resolve());null!=t&&P(t),v$1(e=e??1e3);const n=U(),r=Date.now();return t.then((...t)=>{const o=Date.now()-r;o<e?setTimeout(()=>n.resolve(...t),e-o):n.resolve(...t);}).catch(t=>{const o=Date.now()-r;o<e?setTimeout(()=>n.reject(t),e-o):n.reject(t);}),n.promise},timeout:function(t,e,n){P(t),v$1(e=e??1);const r=U(e,n),o=Date.now();return t.then((...t)=>{Date.now()-o<=e?r.resolve(...t):r.reject(new Error(n??`Promise Timeout: ${e}ms`));}).catch(t=>{!r.resolved&&!r.rejected&&r.reject(t);}),r.promise},allSettled:C,returnValuePromised:I,series:async function(t){$(t);const e=[];for(const n of t)S(n),e.push(await n());return e},seriesAllSettled:async function(t){$(t);const e=[];for(const n of t){S(n);try{e.push({ok:!0,result:await n()});}catch(t){e.push({ok:false,result:t});}}return e},parallel:async function(t,e=5){if($(t),v$1(e),e<=0)throw new Error(`Invalid maxParallel: ${e}, should > 0`);t.forEach(t=>S(t));const n=[];if(t.length<=e){const e=await Promise.all(t.map(t=>I(t)));return n.push(...e),n}const r=[];for(const o of t)if(S(o),r.push(o),r.length>=e){const t=await Promise.all(r.map(t=>I(t)));n.push(...t),r.length=0;}if(r.length>0&&r.length<e){const t=await Promise.all(r.map(t=>I(t)));n.push(...t);}return n},parallelAllSettled:async function(t,e=5){if($(t),v$1(e),e<=0)throw new Error(`Invalid maxParallel: ${e}, should > 0`);t.forEach(t=>S(t));const n=[];if(t.length<=e){const e=await C(t.map(t=>I(t)));return n.push(...e),n}const r=[];for(const o of t)if(S(o),r.push(o),r.length>=e){const t=await C(r.map(t=>I(t)));n.push(...t),r.length=0;}if(r.length>0&&r.length<e){const t=await C(r.map(t=>I(t)));n.push(...t);}return n}};function U(t=-1,e){v$1(t);const n={};let r;return t>=0&&(n.timerHandler=r=setTimeout(()=>{clearTimeout(r),n.timerCleared=true,n.reject(new Error(e??`Promise Timeout: ${t}ms`));},t),n.timerHandler=r),n.promise=new Promise((t,e)=>{n.resolve=(...e)=>{null!=r&&(clearTimeout(r),n.timerCleared=true),n.resolved=true,t(...e);},n.reject=t=>{null!=r&&(clearTimeout(r),n.timerCleared=true),n.rejected=true,e(t);};}),n.promise.cancel=()=>{null!=r&&(clearTimeout(r),n.timerCleared=true),n.rejected=true,n.canceled=n.promise.canceled=true,n.reject(new Error("Cancelled"));},n}async function C(t){$(t);const e=await Promise.allSettled(t),n=[];for(const t of e)"fulfilled"===t.status&&n.push({ok:true,result:t.value}),"rejected"===t.status&&n.push({ok:false,result:t.reason});return n}function I(t){try{const e=t();return d$1(e)?e:Promise.resolve(e)}catch(t){return Promise.reject(t)}} // module vars const DefaultMinInterval = 50; const DefaultMaxInterval = 30 * 1000; // 30s const DefaultMaxRetries = 3; // internal // module vars const { assertPositive: assertPositive$5, assertNotNegative: assertNotNegative$3 } = b$1; const { isNumber } = r$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; 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:s};function t(e){return "function"==typeof e}function s(e){return null==e}function n(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=${JSON.stringify(e)}`)},assertFunction:function(e,s){if(!t(e))throw new Error(`${s?'"'+s+'" ':" "}Not Function: type=${typeof e} value=${JSON.stringify(e)}`)},assertNotNil:function(e,t){if(s(e))throw new Error((t?'"'+t+'" ':" ")+"Should Not Nil")},assertString:function(e,t){if(!n(e))throw new Error(`${t?'"'+t+'" ':" "}Not String: type=${typeof e} value=${JSON.stringify(e)}`)},assertStringOrSymbol:function(e,t){if(!n(e)&&!function(e){return null!=e&&"symbol"==typeof e}(e))throw new Error(`${t?'"'+t+'" ':" "}Not String or Symbol: type=${typeof e} value=${JSON.stringify(e)}`)}};const i="DOwner$#$",{assertFunction:l,assertNotNil:a}=r;class o{constructor(e,t,s=false){a(e,"event"),l(t,"callback"),this._event=e,this._callback=t,this._isOnce=!!s,this._owner=void 0;}set owner(e){this._owner=e;}get owner(){return this._owner===i?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:c,isNil:h}=e,{assertStringOrSymbol:u,assertFunction:_}=r;class f{static get DefaultOwner(){return i}constructor(e){u(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}hasListener(e){return !!c(e)&&this._callbacks.has(e)}hasOwner(e){return !h(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,s,n){if(h(e))return false;_(e),this._callbacks.has(e)||this._callbacks.add(e),t=t??i;const r=new o(this,e,s);r.owner=t,n?this._listeners.unshift(r):this._listeners.push(r),this._listener2Owner.set(r,t);let l=this._callback2Listeners.get(e);null==l&&(l=new Set,this._callback2Listeners.set(e,l)),l.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(h(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 s=this._owner2Listeners.get(t);null!=s&&(s.delete(e),0===s.size&&this._owner2Listeners.delete(t));}return true}_remove(e){const t=this._listeners.indexOf(e);-1!==t&&this._listeners.splice(t,1);const{callback:s}=e,n=this._callback2Listeners.get(s);null!=n&&(n.delete(e),0===n.size&&(this._callback2Listeners.delete(s),this._callbacks.delete(s)));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(h(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,s=this._callback2Listeners.get(t);null!=s&&(s.delete(e),0===s.size&&(this._callback2Listeners.delete(t),this._callbacks.delete(t)));}return this}}const{isNil:m}=e,{assertString:d,assertFunction:L,assertNumber:w,assertStringOrSymbol:v,assertNotNil:g}=r,p=["on","once","addListener","prependListener","prependOnceListener","off","offAll","offOwner","removeAllListeners","removeListener","emit","setMaxListeners","getMaxListeners","hasOwner","listeners","listenerCount","eventNames","rawListeners"];let b=10;class O{static mixin(e){const t=new O;e.__emitter=t;for(const s of p){const n=t[s];e[s]=n.bind(t);}return e}static get defaultMaxListeners(){return b}static set defaultMaxListeners(e){w(e),b=e??10;}constructor(){this._name2Event=new Map,this._maxListeners=b;}addListener(e,t,s){return this.on(e,t,s)}prependListener(e,t,s){d(e),L(t),this._checkMaxListeners(e);return this._getOrCreateEvent(e).prependListener(t,s),this}prependOnceListener(e,t,s){d(e),L(t),this._checkMaxListeners(e);return this._getOrCreateEvent(e).prependOnceListener(t,s),this}emit(e,...t){const s=this._name2Event.get(e);return null!=s&&!s.isEmpty()&&(s.emit(...t),true)}eventNames(){return [...this._name2Event.keys()]}getMaxListeners(){return this._maxListeners}listenerCount(e,t){v(e,"eventName");const s=this._name2Event.get(e);return null==s||s.isEmpty()?0:s.listenerCount(t)}listeners(e){v(e,"eventName");const t=this._name2Event.get(e);return null==t||t.isEmpty()?[]:t.callbacks()}off(e,t){const s=this._name2Event.get(e);return null==s?this:(s.removeListener(t),s.isEmpty()?(this._name2Event.delete(e),this):this)}offAll(e,t){v(e,"eventName");const s=this._name2Event.get(e);return null==s?this:(s.removeAllListeners(t),s.isEmpty()?(this._name2Event.delete(e),this):this)}offOwner(e){g(e,"owner");const t=[...this._name2Event.values()];for(const s of t)s.removeAllListeners(e),s.isEmpty()&&this._name2Event.delete(s.name);return this}on(e,t,s){d(e),L(t),this._checkMaxListeners(e);return this._getOrCreateEvent(e).addListener(t,s),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,s){d(e),L(t);return this._getOrCreateEvent(e).addOnceListener(t,s),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(w(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 f(e);return this._name2Event.set(e,t),t}hasOwner(e){if(m(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 } = b$1; 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(); assertPositive$4(interval, 'interval'); this._interval = interval; } set interval (interval) { assertPositive$4(interval, 'interval'); this._interval = interval; } get interval () { 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 } = b$1; 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 } = b$1; 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 } = b$1; 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 } } // owned /** * @typedef {import('./retrier.js').default} Retrier */ // module vars const { assertNotNil, assertFunction: assertFunction$1 } = b$1; 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; 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; } } 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(); } } } // internal // module vars const { assertNotNegative: assertNotNegative$2 } = b$1; /** * @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 } = b$1; /** * @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 } = b$1; /** * @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; } } // internal // module vars const { assertPositive, assertString, assertFunction, assertNumber } = b$1; const { isNil } = r$1; const TaskTimoutFlag = '!#@%$&^*'; /** * @extends EventEmitter */ class Retrier { /** * 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) { O.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 {{resolve:Function, reject:Function, promise: Promise<*>}|undefined} */ this._taskingFlag = undefined; /** * A Deferred Object as Singal to prevent Task concurrent stop * @type {{resolve:Function, reject:Function, promise: Promise<*>}|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, 'retrierName'); this._name = retrierName; return this } /** * Sets the retry attempts to be infinite by setting max retries to maximum safe integer. * @returns {Object} The retrier instance for chaining. */ 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) { 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._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._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._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._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) { 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) { 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) { 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) { 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) { 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) { 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) { 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 duration in milliseconds. * @returns {Object} The retrier instance for chaining. */ taskTimeout (timeout) { assertPositive(timeout, 'timeout'); this._taskTimeout = timeout; return this } /** * Sets the timeout duration for all retries. * 1. <= 0 - no timeout * 2. \> 0 - timeout duration in milliseconds * @param {number} timeout - The timeout duration in milliseconds. * @returns {Object} The retrier instance for chaining. */ timeout (timeout) { assertNumber(timeout, 'timeout'); this._timeout = timeout; return this } /** * Sets the task function to be retried. * @param {Function} task - The function to be executed and retried on failure. * @returns {this} Returns the retrier instance for chaining. */ task (task) { assertFunction(task, 'task'); this._task = new Task(this, task); return this } /** * alias of {@linkcode Retrier.task()} * @param {Function} task - The function to be executed and retried * @return {this} */ retry (task) { this.task(task); return this } /** * Executes the given task, and never stop * 1. if the task fails, will retry it after the interval generated by RetryPolicy * 2. if the task succeeds, reset RetryPolicy to Minimum Interval and continue to run the task * @param {Function} task - The async function to execute and retry. * @param {boolean} [resetAfterSuccess=false] - Whether to reset retry counters after success. * @returns {this} The Retrier instance for chaining. */ always (task, resetAfterSuccess = false) { this._task = new AlwaysTask(this, task, resetAfterSuccess); return this } /** * Starts the retry process. * @returns {Promise<*>} */ async start () { if (this._task == null) { throw new Error('No Task to Retry') } if (this._taskingFlag != null) { return this._taskingFlag.promise } const startAt = Date.now(); let lastError = null; // @ts-ignore this.emit(Event.Start, startAt); this._taskingFlag = B.defer(); let latency = null; while (true) { // need to stop? if (this._breakFlag != null) { this._taskingFlag.reject(this._breakReason); break } latency = Date.now() - startAt; // total timeout? if (!isInfinite(this._timeout) && latency >= this._timeout) { // total timeout // @ts-ignore this.emit(Event.Timeout, this._currentRetries, latency, this._timeout); // always task, treat as success, resolve the whole promise with <void> if (AlwaysTask.isAlwaysTask(this._task)) { this._taskingFlag.resolve(); break } this._taskingFlag.reject(lastError ?? new Error(`Timeout "${this._timeout}" Exceeded`)); break } // @ts-ignore this.emit(Event.Retry, this._currentRetries, latency); const task = this._task; // take task, it may be changed in events' callback functions const nextDelay = this._policy.generate(this._currentRetries); try { try { await B.timeout(task.execute(this._currentRetries, latency, nextDelay), this._taskTimeout, TaskTimoutFlag); } catch (err) { // @ts-ignore if (err.message === TaskTimoutFlag) { // @ts-ignore this.emit(Event.TaskTimeout, this._currentRetries, latency, this._taskTimeout); } throw err } // @ts-ignore if (task.failed) { lastError = task.error; throw task.error } const rtnVal = task.result; // @ts-ignore this.emit(Event.Success, rtnVal, this._currentRetries, latency); // Not AwaysTask, we can finish all the retries with success if (!AlwaysTask.isAlwaysTask(task)) { this._taskingFlag.resolve(rtnVal); break } // AwaysTask, continue to run the task } catch (e) { // @ts-ignore this.emit(Event.Failure, e, this._currentRetries, latency); } const nextRetries = ++this._currentRetries; // next retry, max retries reached? if (this._currentRetries > this._maxRetries) { // @ts-ignore this.emit(Event.MaxRetries, nextRetries, this._maxRetries); // always task, treat as success, resolve the whole promise with <void> if (AlwaysTask.isAlwaysTask(task)) { this._taskingFlag.resolve(); break } this._taskingFlag.reject(lastError ?? new Error(`Max Retries Exceeded, Retring ${this._currentRetries} times > max ${this._maxRetries}`)); break } await B.delay(nextDelay); } this._taskingFlag.promise.finally(() => { this.resetRetryPolicy(); this._taskingFlag = undefined; const spent = Date.now() - startAt; // @ts-ignore this.emit(Event.Completed, this._currentRetries, spent); }); return this._taskingFlag.promise } /** * Stops the retrier with an optional reason. If already stopping, returns the existing break promise. * @param {Error} [reason] - Optional reason for stopping (defaults to 'Manually Stop' error). * @returns {Promise<void>} A promise that resolves when the retrier has fully stopped. */ async stop (reason) { // @ts-ignore this.emit(Event.Stop, reason); if (this._taskingFlag == null) { return // no task running } if (this._breakFlag != null) { // @ts-ignore return this._breakFlag.promise } this._breakFlag = B.defer(); this._breakReason = reason ?? new Error('Manually Stop'); // @ts-ignore this.once(Event.Completed, () => { // @ts-ignore this._breakFlag.resolve(); }); // @ts-ignore this._breakFlag.promise.finally(() => { this._breakFlag = undefined; this._breakReason = undefined; }); return this._breakFlag.promise } /** * Resets the retry policy to its initial state. */ resetRetryPolicy () { this._policy.reset(); } /** * Registers a listener function to be called on "retry" events. * @param {Function} listener - The callback function * @returns {this} */ onRetry (listener) { // @ts-ignore this.on(Event.Retry, listener); return this } /** * Registers a listener for "error" events. * @param {Function} listener - The callback function * @returns {this} */ onError (listener) { // @ts-ignore this.on(Event.Error, listener); return this } /** * Registers a listener for "failure" events. * @param {Function} listener - The callback function * @returns {this} */ onFailure (listener) { // @ts-ignore this.on(Event.Failure, listener); return this } /** * Registers a listener for "success" events. * @param {Function} listener - The callback function * @returns {this} */ onSuccess (listener) { // @ts-ignore this.on(Event.Success, listener); return this } /** * Registers a listener for "start" events. * @param {Function} listener - The callback function * @returns {this} */ onStart (listener) { // @ts-ignore this.on(Event.Start, listener); return this } /** * Registers a listener for "stop" events. * @param {Function} listener - The callback function * @returns {this} */ onStop (listener) { // @ts-ignore this.on(Event.Stop, listener); return this } /** * Registers a listener for "timeout" events. * @param {Function} listener - The callback function */ onTimeout (listener) { // @ts-ignore this.on(Event.Timeout, listener); return this } /** * Registers a listener for "task-timeout" events. * @param {Function} listener - The callback function * @returns {this} */ onTaskTimeout (listener) { // @ts-ignore this.on(Event.TaskTimeout, listener); return this } /** * Registers a listener for "completed" events. * @param {Function} listener - The callback function */ onCompleted (listener) { // @ts-ignore this.on(Event.Completed, listener); return this } /** * Registers a listener for the 'MaxRetries' event. * @param {Function} listener - The callback function to be executed when max retries are reached. * @returns {this} */ onMaxRetries (listener) { // @ts-ignore this.on(Event.MaxRetries, listener); return this } } /** * Checks if a value represents infinity or a non-positive number. * @param {number} value - The value to check * @returns {boolean} True if the value is <= 0 or Infinity, false otherwise */ function isInfinite (value) { return value <= 0 || value === Infinity } // owned /** * 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. */ function name (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. */ function 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. */ function 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. */ function 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. */ function 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. */ function 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. */ function 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. */ function 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. */ function 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. */ function 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. */ function 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. */ function 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 */ function 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. */ function 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. */ function 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. */ function 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 function to be retried. * @returns {Promise<*>} A promise that resolves when the retry process completes. */ function start (task) { const retrier = new Retrier(); retrier.task(task); return retrier.start() } // default export var RetrierFactory = { name, infinite, times, maxRetries, min, max, range, fixedInterval, fixedBackoff, fixedIncrease, linearBackoff, factorIncrease, exponentialBackoff, shuttleInterval, timeout, taskTimeout, start }; // 3rd /** * Add all factory methods to Retrier as static methods. * Now we can create do something like this: * ``` * Retrier.name('myRetrier') * Retrier.infinite() * ... * ``` */ t$1.defaults(Retrier, RetrierFactory); /** * default export to support * 1. import CommonsRetrier from '@creejs/commons-retrier' */ var index = { Policy, Retrier, Event, RetrierFactory, ...RetrierFactory }; exports.Event = Event; exports.Policy = Policy; exports.Retrier = Retrier; exports.RetrierFactory = RetrierFactory; exports.default = index; exports.exponentialBackoff = exponentialBackoff; exports.factorIncrease = factorIncrease; exports.fixedBackoff = fixedBackoff; exports.fixedIncrease = fixedIncrease; exports.fixedInterval = fixedInterval; exports.infinite = infinite; exports.linearBackoff = linearBackoff; exports.max = max; exports.maxRetries = maxRetries; exports.min = min; exports.name = name; exports.range = range; exports.shuttleInterval = shuttleInterval; exports.start = start; exports.taskTimeout = taskTimeout; exports.timeout = timeout; exports.times = times; //# sourceMappingURL=index-dev.cjs.map