← back to Animate Museum Posts

src/utils/retry.js

73 lines

/**
 * Retry utility with exponential backoff
 */

export async function withRetry(fn, options = {}) {
  const {
    maxRetries = 3,
    initialDelay = 1000,
    maxDelay = 30000,
    backoffFactor = 2,
    onRetry = null
  } = options;

  let lastError;
  let delay = initialDelay;

  for (let attempt = 1; attempt <= maxRetries; attempt++) {
    try {
      return await fn();
    } catch (error) {
      lastError = error;

      if (attempt === maxRetries) {
        break;
      }

      console.log(`Attempt ${attempt}/${maxRetries} failed: ${error.message}`);
      console.log(`Retrying in ${delay}ms...`);

      if (onRetry) {
        onRetry(error, attempt, delay);
      }

      await sleep(delay);

      // Exponential backoff with max cap
      delay = Math.min(delay * backoffFactor, maxDelay);
    }
  }

  throw lastError;
}

function sleep(ms) {
  return new Promise(resolve => setTimeout(resolve, ms));
}

/**
 * Wrap an async function to never throw - returns null on error
 */
export function safeCall(fn, fallback = null) {
  return async (...args) => {
    try {
      return await fn(...args);
    } catch (error) {
      console.error(`Safe call failed: ${error.message}`);
      return fallback;
    }
  };
}

/**
 * Wrap with timeout
 */
export function withTimeout(promise, ms, message = 'Timeout') {
  return Promise.race([
    promise,
    new Promise((_, reject) =>
      setTimeout(() => reject(new Error(message)), ms)
    )
  ]);
}