Skip to content

Instantly share code, notes, and snippets.

@mimshins
Created September 6, 2025 10:04
Show Gist options
  • Save mimshins/43a0e495054b46e6db7e8b621edac242 to your computer and use it in GitHub Desktop.
Save mimshins/43a0e495054b46e6db7e8b621edac242 to your computer and use it in GitHub Desktop.
Executes an asynchronous action and retries it with an exponential backoff and jitter strategy if it fails.
export type WithRetryOptions = {
/**
* The maximum number of retry attempts.
* Defaults to 5.
*
* @default 5
*/
maxRetries?: number;
/**
* The initial backoff time in milliseconds.
* Defaults to 100ms.
*
* @default 100
*/
initialDelay?: number;
/**
* The maximum backoff time in milliseconds.
* Defaults to 10000ms.
*
* @default 10000
*/
maxDelay?: number;
};
/**
* Executes an asynchronous action and retries it with an exponential backoff and jitter strategy if it fails.
* This is useful for handling temporary errors like network issues or transient server failures.
*
* @param action The asynchronous function to execute and potentially retry.
* @param options Optional configuration for the retry behavior.
*
* @returns A promise that resolves with the result of the action on success, or null if all retry attempts fail.
*/
export const withRetry = async <T>(
action: () => Promise<T>,
options: WithRetryOptions,
) => {
const {
initialDelay = 100,
maxDelay = 10000,
maxRetries = 5,
} = options ?? {};
let attempts = 0;
let lastErr = null;
while (attempts < maxRetries) {
try {
return await action();
} catch (err) {
attempts++;
lastErr = err;
if (attempts >= maxRetries) {
throw lastErr;
}
// Exponential backoff calculation
const baseDelay = initialDelay * Math.pow(2, attempts - 1);
// Add jitter to prevent a thundering herd problem
const jitter = Math.random() * baseDelay;
// Calculate final delay, capping at maxDelay
const delay = Math.min(baseDelay + jitter, maxDelay);
// eslint-disable-next-line no-console
console.warn(
`Attempt ${attempts} failed. Retrying in ${delay.toFixed(2)}ms...`,
);
await new Promise(resolve => {
setTimeout(resolve, delay);
});
}
}
// This part of the code should be unreachable, as an error is thrown once `maxRetries` is reached.
// It is included as a fallback.
// eslint-disable-next-line no-console
console.warn(`This should be unreachable. Something went wrong!`);
return null;
};
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment