Function that makes the API request to retry
Maximum number of retry attempts (default: 3)
Initial delay between retries in milliseconds (default: 1000)
Promise resolving to the API call result
// Retry a Spotify API call up to 5 times
const playbackState = await retryApiCall(
() => spotifyApi.getMyCurrentPlaybackState(),
5,
500
);
export async function retryApiCall<T>(
apiCallFn: () => Promise<T>,
maxRetries: number = 3,
initialDelayMs: number = 1000,
): Promise<T> {
let lastError: Error | null = null;
let currentDelay = initialDelayMs;
for (let attempt = 1; attempt <= maxRetries; attempt++) {
try {
return await apiCallFn();
} catch (error: unknown) {
lastError = error as Error;
const errorMessage = lastError.message || String(lastError);
// If this is the last attempt, log as ERROR
if (attempt === maxRetries) {
saveLog(
`API request failed after ${maxRetries} attempts: ${errorMessage}`,
"ERROR",
);
// Let the error propagate to be handled by the caller
throw new Error(
`API request failed after ${maxRetries} attempts: ${errorMessage}`,
);
}
// Otherwise log as WARNING and retry
saveLog(
`API request failed (attempt ${attempt}/${maxRetries}): ${errorMessage}. Retrying in ${Math.round(currentDelay)}ms...`,
"WARNING",
);
// Wait before retrying with exponential backoff
await new Promise((resolve) => setTimeout(resolve, currentDelay));
// Implement exponential backoff with jitter
currentDelay =
Math.min(currentDelay * 1.5, 10000) * (0.9 + Math.random() * 0.2);
}
}
// This should never be reached due to the throw in the last iteration
throw lastError;
}
Retries an API call with exponential backoff and jitter
Executes an API call and automatically retries it with increasing delay on failure, using a combination of exponential backoff and random jitter to avoid overwhelming the API during recovery periods.
The backoff algorithm works as follows: