Created
March 27, 2025 14:15
-
-
Save DScheglov/49d28090fe64b764437be5649cd2a64c to your computer and use it in GitHub Desktop.
Retry-decorator for fetch
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
type ShouldRetryFn = (err: unknown, res: Response | undefined) => boolean | |
export class RetryFetchError extends TypeError { | |
name = "RetryFetchError" | |
constructor(maxAttempts: number, cause?: unknown) { | |
super(`Failed to fetch after ${maxAttempts} attempts.`, { cause }); | |
if (!Object.prototype.hasOwnProperty.call(this, "cause")) { | |
Object.defineProperty(this, "cause", { value: cause }) | |
} | |
} | |
} | |
export type RetryFetchOptions = { | |
shouldRetry?: ShouldRetryFn; | |
delayBeforeNextAttempt?: number | ((attempt: number) => number); | |
} | |
const createDelay = (delayBeforeNextAttempt?: number | ((attempt: number) => number)) => { | |
const getDelay = | |
delayBeforeNextAttempt == null ? null : | |
typeof delayBeforeNextAttempt === "number" && delayBeforeNextAttempt > 0 | |
? () => delayBeforeNextAttempt : | |
typeof delayBeforeNextAttempt === "function" | |
? delayBeforeNextAttempt as (attempt: number) => number : | |
null; | |
if (getDelay === null) return null; | |
return (attempt: number) => new Promise( | |
resolve => setTimeout(resolve, getDelay(attempt)), | |
); | |
} | |
export const retryFetch = | |
( | |
fetchFn: typeof fetch, | |
maxAttempts: number, | |
{ shouldRetry, delayBeforeNextAttempt = 500 }: RetryFetchOptions = {} | |
) => | |
async (...args: Parameters<typeof fetch>) => { | |
let cause: unknown = null; | |
const delay = createDelay(delayBeforeNextAttempt); | |
for (let attempt = 0; attempt < maxAttempts; attempt++) { | |
try { | |
if (attempt > 0 && delay !== null) await delay(attempt); | |
const res = await fetchFn(...args); | |
const retry = shouldRetry?.(undefined, res) ?? false; | |
if (!retry) return res; | |
cause = res; | |
} catch(err) { | |
const retry = shouldRetry?.(err, undefined) ?? true; | |
if (!retry) throw err; | |
cause = err; | |
} | |
} | |
throw new RetryFetchError(maxAttempts, cause); | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment