UNPKG

@atomiqlabs/base

Version:

Base classes and interfaces for atomiq protocol

74 lines (64 loc) 2.97 kB
import {getLogger} from "./Logger"; import {timeoutPromise} from "./TimeoutUtils"; const logger = getLogger("RetryUtils: "); type Constructor<T = any> = new (...args: any[]) => T; function isConstructor(fn: any): fn is Constructor { return ( typeof fn === 'function' && fn.prototype != null && fn.prototype.constructor === fn ); } function isConstructorArray(fnArr: any): fnArr is Constructor[] { return Array.isArray(fnArr) && fnArr.every(isConstructor); } /** * Checks whether the passed error is allowed to pass through * * @param e Error in question * @param errorAllowed Allowed errors as defined as a callback function, specific error type, or an array of error types */ function checkError(e: any, errorAllowed: ((e: any) => boolean) | Constructor<Error> | Constructor<Error>[]) { if (isConstructorArray(errorAllowed)) return errorAllowed.find(error => e instanceof error) != null; if (isConstructor(errorAllowed)) return e instanceof errorAllowed; return errorAllowed(e); } /** * Runs the passed function multiple times if it fails * * @param func A callback for executing the action * @param func.retryCount Count of the current retry, starting from 0 for original request and increasing * @param retryPolicy Retry policy * @param retryPolicy.maxRetries How many retries to attempt in total * @param retryPolicy.delay How long should the delay be * @param retryPolicy.exponential Whether to use exponentially increasing delays * @param errorAllowed A callback for determining whether a given error is allowed, and we should therefore not retry * @param abortSignal * * @returns Result of the action executing callback * * @internal */ export async function tryWithRetries<T>(func: (retryCount: number) => Promise<T>, retryPolicy?: { maxRetries?: number, delay?: number, exponential?: boolean }, errorAllowed?: ((e: any) => boolean) | Constructor<Error> | Constructor<Error>[], abortSignal?: AbortSignal): Promise<T> { retryPolicy = retryPolicy || {}; retryPolicy.maxRetries = retryPolicy.maxRetries || 5; retryPolicy.delay = retryPolicy.delay || 500; retryPolicy.exponential = retryPolicy.exponential == null ? true : retryPolicy.exponential; let err = null; for (let i = 0; i < retryPolicy.maxRetries; i++) { try { return await func(i); } catch (e) { if (errorAllowed != null && checkError(e, errorAllowed)) throw e; err = e; logger.debug("tryWithRetries(): Error on try number: " + i, e); } if (abortSignal != null && abortSignal.aborted) throw (abortSignal.reason || new Error("Aborted")); if (i !== retryPolicy.maxRetries - 1) { await timeoutPromise(retryPolicy.exponential ? retryPolicy.delay * Math.pow(2, i) : retryPolicy.delay, abortSignal); } } throw err; }