@naturalcycles/js-lib
Version:
Standard library for universal (browser + Node.js) javascript
68 lines (67 loc) • 2.7 kB
JavaScript
import { _since } from '../datetime/time.util.js';
import { _errorDataAppend } from '../error/error.util.js';
import { pDelay } from './pDelay.js';
import { pTimeout } from './pTimeout.js';
/**
* Returns a Function (!), enhanced with retry capabilities.
* Implements "Exponential back-off strategy" by multiplying the delay by `delayMultiplier` with each try.
*/
export function pRetryFn(fn, opt = {}) {
return async function pRetryFunction(...args) {
return await pRetry(() => fn.call(this, ...args), opt);
};
}
export async function pRetry(fn, opt = {}) {
const { maxAttempts = 4, delay: initialDelay = 1000, delayMultiplier = 2, predicate, logger = console, name, timeout, } = opt;
const fakeError = timeout ? new Error('TimeoutError') : undefined;
let { logFirstAttempt = false, logRetries = true, logFailures = true, logSuccess = false } = opt;
if (opt.logAll) {
logSuccess = logFirstAttempt = logRetries = logFailures = true;
}
if (opt.logNone) {
logSuccess = logFirstAttempt = logRetries = logFailures = false;
}
const fname = name || fn.name || 'pRetry function';
let delay = initialDelay;
let attempt = 0;
while (true) {
const started = Date.now();
try {
attempt++;
if ((attempt === 1 && logFirstAttempt) || (attempt > 1 && logRetries)) {
logger.log(`${fname} attempt #${attempt}...`);
}
let result;
if (timeout) {
result = await pTimeout(async () => await fn(attempt), {
timeout,
name: fname,
errorData: opt.errorData,
fakeError,
});
}
else {
result = await fn(attempt);
}
if (logSuccess) {
logger.log(`${fname} attempt #${attempt} succeeded in ${_since(started)}`);
}
return result;
}
catch (err) {
if (logFailures) {
// Logger at warn (not error) level, because it's not a fatal error, but a retriable one
// Fatal one is not logged either, because it's been thrown instead
logger.warn(`${fname} attempt #${attempt} error in ${_since(started)}:`, err);
}
if (attempt >= maxAttempts || (predicate && !predicate(err, attempt, maxAttempts))) {
// Give up
throw _errorDataAppend(err, opt.errorData);
}
// Retry after delay
delay *= delayMultiplier;
await pDelay(delay);
// back to while(true) loop
}
}
}