@lpezet/p-retry-ts
Version:
Retry a promise-returning or async function
125 lines (124 loc) • 4.91 kB
JavaScript
;
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;