Skip to content

retry

Retry async operations until success or maximum retry attempts are reached.

Basic Usage

typescript
import { retry } from 'radash'

const result = await retry(async () => {
  const response = await fetch('https://api.example.com/data')
  if (!response.ok) {
    throw new Error(`HTTP ${response.status}`)
  }
  return response.json()
}, { attempts: 3 })

console.log(result)

Syntax

typescript
function retry<T>(
  fn: () => Promise<T>,
  options?: {
    attempts?: number
    delay?: number
    backoff?: boolean
    onRetry?: (error: Error, attempt: number) => void
  }
): Promise<T>

Parameters

  • fn (function): The async function to retry
  • options (object, optional): Retry options
    • attempts (number, optional): Maximum retry attempts, defaults to 3
    • delay (number, optional): Retry delay in milliseconds, defaults to 1000
    • backoff (boolean, optional): Whether to use exponential backoff, defaults to false
    • onRetry (function, optional): Callback function on retry

Return Value

Returns a Promise that resolves to the final result of the function.

Examples

Basic Retry

typescript
import { retry } from 'radash'

const result = await retry(async () => {
  const response = await fetch('https://api.example.com/data')
  if (!response.ok) {
    throw new Error(`HTTP ${response.status}`)
  }
  return response.json()
}, { attempts: 3 })

console.log(result)

Custom Retry Count and Delay

typescript
import { retry } from 'radash'

const result = await retry(async () => {
  const response = await fetch('https://api.example.com/unreliable')
  if (!response.ok) {
    throw new Error(`Request failed: ${response.status}`)
  }
  return response.json()
}, { 
  attempts: 5,
  delay: 2000 // 2 second delay
})

console.log('Success:', result)

Using Exponential Backoff

typescript
import { retry } from 'radash'

const result = await retry(async () => {
  const response = await fetch('https://api.example.com/data')
  if (!response.ok) {
    throw new Error(`HTTP ${response.status}`)
  }
  return response.json()
}, { 
  attempts: 3,
  backoff: true // Use exponential backoff
})

console.log(result)

With Retry Callback

typescript
import { retry } from 'radash'

const result = await retry(async () => {
  const response = await fetch('https://api.example.com/data')
  if (!response.ok) {
    throw new Error(`HTTP ${response.status}`)
  }
  return response.json()
}, { 
  attempts: 3,
  onRetry: (error, attempt) => {
    console.log(`Attempt ${attempt} failed:`, error.message)
  }
})

console.log('Final result:', result)

Handle Different Error Types

typescript
import { retry } from 'radash'

const result = await retry(async () => {
  const response = await fetch('https://api.example.com/data')
  
  if (response.status === 429) {
    throw new Error('Rate limited')
  }
  
  if (response.status >= 500) {
    throw new Error('Server error')
  }
  
  if (!response.ok) {
    throw new Error(`HTTP ${response.status}`)
  }
  
  return response.json()
}, { 
  attempts: 5,
  delay: 1000,
  onRetry: (error, attempt) => {
    if (error.message === 'Rate limited') {
      console.log(`Rate limited, waiting before attempt ${attempt + 1}`)
    } else {
      console.log(`Server error on attempt ${attempt}, retrying...`)
    }
  }
})

Database Connection Retry

typescript
import { retry } from 'radash'

async function connectToDatabase() {
  return await retry(async () => {
    const connection = await createDatabaseConnection()
    
    // Test the connection
    await connection.query('SELECT 1')
    
    return connection
  }, {
    attempts: 5,
    delay: 2000,
    backoff: true,
    onRetry: (error, attempt) => {
      console.log(`Database connection attempt ${attempt} failed:`, error.message)
    }
  })
}

const db = await connectToDatabase()
console.log('Database connected successfully')

File Operation Retry

typescript
import { retry } from 'radash'

async function readFileWithRetry(filePath: string) {
  return await retry(async () => {
    const fs = await import('fs/promises')
    const content = await fs.readFile(filePath, 'utf-8')
    return content
  }, {
    attempts: 3,
    delay: 1000,
    onRetry: (error, attempt) => {
      console.log(`File read attempt ${attempt} failed:`, error.message)
    }
  })
}

const content = await readFileWithRetry('./config.json')
console.log('File content:', content)

API Rate Limiting

typescript
import { retry } from 'radash'

async function fetchWithRateLimit(url: string) {
  return await retry(async () => {
    const response = await fetch(url)
    
    if (response.status === 429) {
      const retryAfter = response.headers.get('Retry-After')
      const delay = retryAfter ? parseInt(retryAfter) * 1000 : 5000
      
      throw new Error(`Rate limited, retry after ${delay}ms`)
    }
    
    if (!response.ok) {
      throw new Error(`HTTP ${response.status}`)
    }
    
    return response.json()
  }, {
    attempts: 3,
    delay: 1000,
    onRetry: (error, attempt) => {
      if (error.message.includes('Rate limited')) {
        const delay = parseInt(error.message.match(/\d+/)?.[0] || '5000')
        console.log(`Rate limited, waiting ${delay}ms before retry`)
        return delay
      }
      return 1000
    }
  })
}

const data = await fetchWithRateLimit('https://api.example.com/data')

Conditional Retry

typescript
import { retry } from 'radash'

async function processWithConditionalRetry(data: any) {
  return await retry(async () => {
    const response = await fetch('/api/process', {
      method: 'POST',
      headers: { 'Content-Type': 'application/json' },
      body: JSON.stringify(data)
    })
    
    if (!response.ok) {
      const error = await response.json()
      
      // Only retry on specific error types
      if (error.code === 'TEMPORARY_ERROR' || error.code === 'RATE_LIMIT') {
        throw new Error(error.message)
      } else {
        // Don't retry on permanent errors
        throw new Error('Permanent error, not retrying')
      }
    }
    
    return response.json()
  }, {
    attempts: 3,
    delay: 1000,
    onRetry: (error, attempt) => {
      if (error.message.includes('Permanent error')) {
        throw error // Stop retrying
      }
      console.log(`Retrying on attempt ${attempt}:`, error.message)
    }
  })
}

Timeout with Retry

typescript
import { retry } from 'radash'

async function fetchWithTimeoutAndRetry(url: string, timeout: number = 5000) {
  return await retry(async () => {
    const controller = new AbortController()
    const timeoutId = setTimeout(() => controller.abort(), timeout)
    
    try {
      const response = await fetch(url, { signal: controller.signal })
      clearTimeout(timeoutId)
      
      if (!response.ok) {
        throw new Error(`HTTP ${response.status}`)
      }
      
      return response.json()
    } catch (error) {
      clearTimeout(timeoutId)
      
      if (error.name === 'AbortError') {
        throw new Error('Request timeout')
      }
      
      throw error
    }
  }, {
    attempts: 3,
    delay: 1000,
    onRetry: (error, attempt) => {
      console.log(`Attempt ${attempt} failed:`, error.message)
    }
  })
}

const data = await fetchWithTimeoutAndRetry('https://api.example.com/slow', 3000)

Batch Processing with Retry

typescript
import { retry } from 'radash'

async function processBatchWithRetry(items: any[]) {
  const results = await Promise.all(
    items.map(item => 
      retry(async () => {
        const response = await fetch('/api/process', {
          method: 'POST',
          headers: { 'Content-Type': 'application/json' },
          body: JSON.stringify(item)
        })
        
        if (!response.ok) {
          throw new Error(`Processing failed: ${response.status}`)
        }
        
        return response.json()
      }, {
        attempts: 3,
        delay: 1000,
        onRetry: (error, attempt) => {
          console.log(`Item processing attempt ${attempt} failed:`, error.message)
        }
      })
    )
  )
  
  return results
}

const items = [{ id: 1 }, { id: 2 }, { id: 3 }]
const processedItems = await processBatchWithRetry(items)

Custom Retry Strategy

typescript
import { retry } from 'radash'

class CustomRetryStrategy {
  private attemptCount = 0
  private lastError: Error | null = null
  
  async execute<T>(fn: () => Promise<T>): Promise<T> {
    while (this.attemptCount < 5) {
      try {
        return await fn()
      } catch (error) {
        this.lastError = error as Error
        this.attemptCount++
        
        if (this.attemptCount >= 5) {
          throw error
        }
        
        // Custom delay logic
        const delay = this.calculateDelay()
        await new Promise(resolve => setTimeout(resolve, delay))
        
        console.log(`Retry attempt ${this.attemptCount} after ${delay}ms`)
      }
    }
    
    throw this.lastError!
  }
  
  private calculateDelay(): number {
    // Custom delay calculation
    return Math.min(1000 * Math.pow(2, this.attemptCount - 1), 10000)
  }
}

// Using custom strategy with radash retry
const customRetry = new CustomRetryStrategy()

const result = await retry(async () => {
  return await customRetry.execute(async () => {
    const response = await fetch('https://api.example.com/data')
    if (!response.ok) {
      throw new Error(`HTTP ${response.status}`)
    }
    return response.json()
  })
}, { attempts: 1 }) // Only one attempt since custom strategy handles retries

Notes

  1. Error handling: Only retries on thrown errors
  2. Exponential backoff: Increases delay exponentially with each retry
  3. Custom delays: Can override delay with onRetry callback
  4. Performance: Adds overhead for retry logic
  5. Timeout: Consider adding timeout to prevent infinite retries

Differences from Other Functions

  • retry: Handles retry logic for async operations
  • guard: Protects operations but doesn't retry
  • Promise.catch(): Only handles errors, doesn't retry
  • retry(): Provides structured retry mechanism

Performance

  • Time Complexity: O(n) where n is number of attempts
  • Memory: Minimal additional memory usage
  • Use Cases: Network requests, database operations, file operations

Released under the MIT License.