UNPKG

@lpezet/p-retry-ts

Version:

Retry a promise-returning or async function

125 lines (124 loc) 4.91 kB
"use strict"; var __importDefault = (this && this.__importDefault) || function (mod) { return (mod && mod.__esModule) ? mod : { "default": mod }; }; Object.defineProperty(exports, "__esModule", { value: true }); exports.FailedAttemptError = exports.AbortError = void 0; const retry_1 = __importDefault(require("retry")); const isNetworkError_1 = __importDefault(require("./isNetworkError")); class AbortError extends Error { constructor(message) { super(); Object.defineProperty(this, "originalError", { enumerable: true, configurable: true, writable: true, value: void 0 }); if (message instanceof Error) { this.originalError = message; ({ message } = message); } else { this.originalError = new Error(message); this.originalError.stack = this.stack; } this.name = 'AbortError'; this.message = message; } } exports.AbortError = AbortError; class FailedAttemptError extends Error { constructor(cause) { super(cause === null || cause === void 0 ? void 0 : cause.message); Object.defineProperty(this, "attemptNumber", { enumerable: true, configurable: true, writable: true, value: 0 }); Object.defineProperty(this, "retriesLeft", { enumerable: true, configurable: true, writable: true, value: 0 }); Object.defineProperty(this, "cause", { enumerable: true, configurable: true, writable: true, value: void 0 }); this.cause = cause; } } exports.FailedAttemptError = FailedAttemptError; const decorateErrorWithCounts = (error, attemptNumber, options) => { // Minus 1 from attemptNumber because the first attempt does not count as a retry const retriesLeft = (options.retries || 0) - (attemptNumber - 1); const fae = new FailedAttemptError(error); fae.attemptNumber = attemptNumber; fae.retriesLeft = retriesLeft; return fae; }; async function pRetry(input, options) { return new Promise((resolve, reject) => { const defaultedOptions = { onFailedAttempt() { }, // eslint-disable-line @typescript-eslint/no-empty-function retries: 10, shouldRetry: () => true, ...options, }; const operation = retry_1.default.operation(defaultedOptions); const abortHandler = () => { var _a; operation.stop(); reject((_a = defaultedOptions.signal) === null || _a === void 0 ? void 0 : _a.reason); }; if (defaultedOptions.signal && !defaultedOptions.signal.aborted) { defaultedOptions.signal.addEventListener('abort', abortHandler, { once: true }); } const cleanUp = () => { var _a; (_a = defaultedOptions.signal) === null || _a === void 0 ? void 0 : _a.removeEventListener('abort', abortHandler); operation.stop(); }; operation.attempt(async (attemptNumber) => { try { const result = await input(attemptNumber); cleanUp(); resolve(result); } catch (error) { try { if (!(error instanceof Error)) { throw new TypeError(`Non-error was thrown: "${error}". You should only throw errors.`); // eslint-disable-line @typescript-eslint/restrict-template-expressions } if (error instanceof AbortError) { throw error.originalError; } if (error instanceof TypeError && !(0, isNetworkError_1.default)(error)) { throw error; // eslint-disable-line @typescript-eslint/no-throw-literal } const fae = decorateErrorWithCounts(error, attemptNumber, defaultedOptions); if (defaultedOptions.shouldRetry && !(await defaultedOptions.shouldRetry(fae))) { operation.stop(); reject(error); } if (defaultedOptions.onFailedAttempt) { await defaultedOptions.onFailedAttempt(fae); } if (!operation.retry(fae)) { throw operation.mainError(); // eslint-disable-line @typescript-eslint/no-throw-literal } } catch (finalError) { const faee = decorateErrorWithCounts(finalError, attemptNumber, defaultedOptions); cleanUp(); reject(faee); } } }); }); } exports.default = pRetry;