Last active
April 29, 2025 13:17
-
-
Save maradondt/6a4181e4137532065c4c7e0e12bf3223 to your computer and use it in GitHub Desktop.
helper fn to retry requests follow different strategies
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
import { sleep } from './utils'; | |
export type RetryOptions = { | |
/** | |
* Maximum Retry Count (Following Circuit Breaker Strategy) | |
* @default 3 | |
*/ | |
retryCount?: number; | |
/** Prevent retry after link has expired */ | |
linkTtlSec?: number | null; | |
/** | |
* Delay in ms between retries | |
* @default 100 | |
*/ | |
delayMs?: number; | |
/** | |
* Exponential Backoff: Increases the delay exponentially with each retry attempt, reducing the load on the system. | |
*/ | |
exponentialMultiplier?: number; | |
/** | |
* Jitter: Adds randomness to the delay to prevent synchronized retries from multiple clients. | |
*/ | |
jitter?: boolean; | |
/** | |
* A function to determine if a retry should occur based on the error. | |
* If provided, retries will only happen if `matcher(error)` returns `true`. | |
*/ | |
matcher?: (error: unknown) => boolean; | |
}; | |
/** | |
* | |
* @example ```ts | |
* retry(download, { | |
* retryCount: 5, | |
* linkTtlSec: ttlSec, | |
* delayMs: 200, | |
* exponentialMultiplier: 2, | |
* }); | |
* | |
* ``` | |
*/ | |
export function retry<T>(cb: () => Promise<T>, options: RetryOptions = {}) { | |
const { retryCount = 3, linkTtlSec, matcher } = options; | |
let counter = 0; | |
const firstStart = performance.now(); | |
const run = async () => { | |
try { | |
counter += 1; | |
return await cb(); | |
} catch (e) { | |
const retryTime = performance.now(); | |
const delay = getDelay(counter, options); | |
const delaySec = getDelay(counter, options) / 1000; | |
const diffFromFirstStartSec = (retryTime - firstStart) / 1000; | |
if (linkTtlSec && diffFromFirstStartSec + delaySec > linkTtlSec) { | |
throw e; | |
} | |
if (counter > retryCount) { | |
throw e; | |
} | |
if (matcher && !matcher(e)) { | |
throw e; | |
} | |
await sleep(delay); | |
return run(); | |
} | |
}; | |
return run(); | |
} | |
function getDelay(count: number, { delayMs, exponentialMultiplier, jitter }: RetryOptions) { | |
let delay = delayMs ?? 100; | |
if (exponentialMultiplier && !jitter) { | |
delay = delay * count * exponentialMultiplier; | |
} | |
if (jitter && !exponentialMultiplier) { | |
delay = delay * Math.random(); | |
} | |
return delay; | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment