• 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:

    1. Initial delay starts at initialDelayMs
    2. After each failure, delay increases by ~1.5x (with small random variation)
    3. Maximum delay is capped at 10 seconds regardless of retry count
    4. Random jitter of ±10% is applied to prevent synchronized retries

    Type Parameters

    • T

    Parameters

    • apiCallFn: () => Promise<T>

      Function that makes the API request to retry

    • maxRetries: number = 3

      Maximum number of retry attempts (default: 3)

    • initialDelayMs: number = 1000

      Initial delay between retries in milliseconds (default: 1000)

    Returns Promise<T>

    Promise resolving to the API call result

    Error when all retry attempts are exhausted

    // 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;
    }