Created
December 30, 2024 09:25
-
-
Save pete-otaqui/ba912db2a18c1076c2a29a7a7fafd442 to your computer and use it in GitHub Desktop.
perform with backoff
This file contains 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
/** | |
* Perform a function with exponential backoff in the event of an error. | |
* | |
* @param fn The function to perform with backoff | |
* @param options Options for the backoff | |
* @returns The result of `fn()` after it has been successfully executed | |
* @throws Any error thrown by `fn()` after the maximum number of attempts has | |
* been reached, or if `options.retryError` returns false | |
* | |
* @example | |
* ```ts | |
* // Perform a synchronous function with backoff | |
* const result1 = await performWithBackoff(() => { | |
* return Math.random() > 0.5; | |
* }); | |
* ``` | |
* | |
* @example | |
* ```ts | |
* // Perform an asynchronous function with backoff | |
* const result2 = await performWithBackoff(async () => { | |
* await new Promise((resolve) => setTimeout(resolve, 1000)); | |
* return Math.random() > 0.5; | |
* }); | |
* ``` | |
* | |
* @example | |
* ```ts | |
* // Only accept certain errors for retrying | |
* class RateLimitError extends Error {} | |
* const result3 = await performWithBackoff(async () => { | |
* // `fetch` might return a 429 status code if the API is rate limiting us | |
* const response = await fetch("https://api.example.com"); | |
* if (response.status === 429) { | |
* throw new RateLimitError('Rate limited'); | |
* } | |
* return response; | |
* }, { | |
* retryError: (error) => error instanceof RateLimitError, | |
* }); | |
* | |
* ``` | |
*/ | |
export async function performWithBackoff<T>( | |
fn: () => Promise<T> | T, | |
{ | |
maxAttempts = 5, | |
calculateBackoffMs = (attempt: number) => { | |
const jitter = Math.random() * attempt * 1000; | |
const backoffMs = jitter + (attempt * attempt * 1000); | |
return backoffMs; | |
}, | |
retryError = (error: any) => true, | |
logError = (error: any, attempt: number) => { | |
console.log(`Attempt: ${attempt} failed with error: ${error}`); | |
} | |
}: { | |
maxAttempts?: number; | |
calculateBackoffMs?: (attempt: number) => number; | |
retryError?: (error: any) => boolean; | |
logError?: (error: any, attempt: number) => void | Promise<void>; | |
} = {}, | |
): Promise<T> { | |
async function actuallyPerformWithBackoff(attempt: number): Promise<T> { | |
try { | |
return await fn(); | |
} catch (e) { | |
if (attempt >= maxAttempts || !retryError(e)) { | |
throw e; | |
} | |
await logError(e, attempt); | |
const backoffMs = calculateBackoffMs(attempt); | |
await new Promise((resolve) => setTimeout(resolve, backoffMs)); | |
return actuallyPerformWithBackoff(attempt + 1); | |
} | |
} | |
return actuallyPerformWithBackoff(1); | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment