Skip to content

Instantly share code, notes, and snippets.

@DScheglov
Created March 27, 2025 14:15
Show Gist options
  • Save DScheglov/49d28090fe64b764437be5649cd2a64c to your computer and use it in GitHub Desktop.
Save DScheglov/49d28090fe64b764437be5649cd2a64c to your computer and use it in GitHub Desktop.
Retry-decorator for fetch
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