2
0

retry.ts 1.2 KB

1234567891011121314151617181920212223242526272829303132333435363738394041
  1. export interface RetryOptions {
  2. attempts?: number
  3. delay?: number
  4. factor?: number
  5. maxDelay?: number
  6. retryIf?: (error: unknown) => boolean
  7. }
  8. const TRANSIENT_MESSAGES = [
  9. "load failed",
  10. "network connection was lost",
  11. "network request failed",
  12. "failed to fetch",
  13. "econnreset",
  14. "econnrefused",
  15. "etimedout",
  16. "socket hang up",
  17. ]
  18. function isTransientError(error: unknown): boolean {
  19. if (!error) return false
  20. const message = String(error instanceof Error ? error.message : error).toLowerCase()
  21. return TRANSIENT_MESSAGES.some((m) => message.includes(m))
  22. }
  23. export async function retry<T>(fn: () => Promise<T>, options: RetryOptions = {}): Promise<T> {
  24. const { attempts = 3, delay = 500, factor = 2, maxDelay = 10000, retryIf = isTransientError } = options
  25. let lastError: unknown
  26. for (let attempt = 0; attempt < attempts; attempt++) {
  27. try {
  28. return await fn()
  29. } catch (error) {
  30. lastError = error
  31. if (attempt === attempts - 1 || !retryIf(error)) throw error
  32. const wait = Math.min(delay * Math.pow(factor, attempt), maxDelay)
  33. await new Promise((resolve) => setTimeout(resolve, wait))
  34. }
  35. }
  36. throw lastError
  37. }