UNPKG

@naturalcycles/js-lib

Version:

Standard library for universal (browser + Node.js) javascript

68 lines (67 loc) 2.7 kB
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 } } }