@atproto/common-web
Version:
Shared web-platform-friendly code for atproto libraries
59 lines (52 loc) • 1.51 kB
text/typescript
import { wait } from './util'
export type RetryOptions = {
maxRetries?: number
getWaitMs?: (n: number) => number | null
}
export async function retry<T>(
fn: () => Promise<T>,
opts: RetryOptions & {
retryable?: (err: unknown) => boolean
} = {},
): Promise<T> {
const { maxRetries = 3, retryable = () => true, getWaitMs = backoffMs } = opts
let retries = 0
let doneError: unknown
while (!doneError) {
try {
return await fn()
} catch (err) {
const waitMs = getWaitMs(retries)
const willRetry =
retries < maxRetries && waitMs !== null && retryable(err)
if (willRetry) {
retries += 1
if (waitMs !== 0) {
await wait(waitMs)
}
} else {
doneError = err
}
}
}
throw doneError
}
export function createRetryable(retryable: (err: unknown) => boolean) {
return async <T>(fn: () => Promise<T>, opts?: RetryOptions) =>
retry(fn, { ...opts, retryable })
}
// Waits exponential backoff with max and jitter: ~100, ~200, ~400, ~800, ~1000, ~1000, ...
export function backoffMs(n: number, multiplier = 100, max = 1000) {
const exponentialMs = Math.pow(2, n) * multiplier
const ms = Math.min(exponentialMs, max)
return jitter(ms)
}
// Adds randomness +/-15% of value
function jitter(value: number) {
const delta = value * 0.15
return value + randomRange(-delta, delta)
}
function randomRange(from: number, to: number) {
const rand = Math.random() * (to - from)
return rand + from
}