Skip to content

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)

語法

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

參數

  • fn (function): 要重試的異步函數
  • options (object, 可選): 重試選項
    • attempts (number, 可選): 最大重試次數,默認為3
    • delay (number, 可選): 重試間隔(毫秒),默認為1000
    • backoff (boolean, 可選): 是否使用指數退避,默認為false
    • onRetry (function, 可選): 重試時的回調函數

返回值

返回一個Promise,解析為函數的最終結果。

示例

基本重試

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)

自定義重試次數和延遲

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秒延遲
})

console.log('Success:', result)

使用指數退避

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,
  delay: 1000,
  backoff: true // 使用指數退避:1s, 2s, 4s
})

console.log(result)

添加重試回調

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(`Retrying in ${1000 * attempt}ms...`)
  }
})

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

處理數據庫操作

typescript
import { retry } from 'radash'

async function createUser(userData: any) {
  return retry(async () => {
    // 模擬數據庫操作
    const response = await fetch('/api/users', {
      method: 'POST',
      headers: { 'Content-Type': 'application/json' },
      body: JSON.stringify(userData)
    })
    
    if (!response.ok) {
      throw new Error(`Failed to create user: ${response.status}`)
    }
    
    return response.json()
  }, {
    attempts: 3,
    delay: 1000,
    backoff: true
  })
}

const newUser = await createUser({
  name: 'John Doe',
  email: 'john@example.com'
})

console.log('User created:', newUser)

處理文件操作

typescript
import { retry } from 'radash'

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

const config = await readFileWithRetry('./config.json')
console.log('Config loaded:', config)

處理條件重試

typescript
import { retry } from 'radash'

async function processWithConditionalRetry(shouldFail: boolean) {
  return retry(async () => {
    if (shouldFail) {
      throw new Error('Operation failed as requested')
    }
    
    // 模擬成功操作
    await new Promise(resolve => setTimeout(resolve, 100))
    return { success: true, timestamp: Date.now() }
  }, {
    attempts: 3,
    delay: 1000,
    onRetry: (error, attempt) => {
      console.log(`Attempt ${attempt} failed:`, error.message)
    }
  })
}

// 成功的情況
const successResult = await processWithConditionalRetry(false)
console.log('Success:', successResult)

// 失敗的情況(會重試3次後失敗)
try {
  const failureResult = await processWithConditionalRetry(true)
  console.log('This should not be reached')
} catch (error) {
  console.log('All attempts failed:', error.message)
}

處理網絡請求重試

typescript
import { retry } from 'radash'

async function fetchWithRetry(url: string) {
  return retry(async () => {
    const controller = new AbortController()
    const timeoutId = setTimeout(() => controller.abort(), 10000) // 10秒超時
    
    try {
      const response = await fetch(url, {
        signal: controller.signal
      })
      
      clearTimeout(timeoutId)
      
      if (!response.ok) {
        throw new Error(`HTTP ${response.status}: ${response.statusText}`)
      }
      
      return response.json()
    } catch (error) {
      clearTimeout(timeoutId)
      throw error
    }
  }, {
    attempts: 3,
    delay: 2000,
    backoff: true,
    onRetry: (error, attempt) => {
      console.log(`Request attempt ${attempt} failed:`, error.message)
    }
  })
}

const data = await fetchWithRetry('https://api.example.com/data')
console.log('Data received:', data)

處理復雜錯誤處理

typescript
import { retry } from 'radash'

class CustomError extends Error {
  constructor(message: string, public code: string) {
    super(message)
    this.name = 'CustomError'
  }
}

async function complexOperation() {
  return retry(async () => {
    const random = Math.random()
    
    if (random < 0.3) {
      throw new CustomError('Network error', 'NETWORK_ERROR')
    } else if (random < 0.6) {
      throw new CustomError('Validation error', 'VALIDATION_ERROR')
    } else if (random < 0.8) {
      throw new CustomError('Server error', 'SERVER_ERROR')
    }
    
    return { message: 'Operation completed successfully' }
  }, {
    attempts: 5,
    delay: 1000,
    backoff: true,
    onRetry: (error, attempt) => {
      if (error instanceof CustomError) {
        switch (error.code) {
          case 'NETWORK_ERROR':
            console.log(`Network issue on attempt ${attempt}, retrying...`)
            break
          case 'VALIDATION_ERROR':
            console.log(`Validation error on attempt ${attempt}, retrying...`)
            break
          case 'SERVER_ERROR':
            console.log(`Server error on attempt ${attempt}, retrying...`)
            break
        }
      } else {
        console.log(`Unknown error on attempt ${attempt}:`, error.message)
      }
    }
  })
}

const result = await complexOperation()
console.log('Final result:', result)

處理批量操作重試

typescript
import { retry } from 'radash'

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

const items = [
  { id: 1, data: 'item1' },
  { id: 2, data: 'item2' },
  { id: 3, data: 'item3' }
]

const results = await processBatchWithRetry(items)
console.log('Batch processed:', results)

處理超時重試

typescript
import { retry } from 'radash'

async function fetchWithTimeoutAndRetry(url: string, timeout: number) {
  return 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)
      throw error
    }
  }, {
    attempts: 3,
    delay: 1000,
    backoff: true,
    onRetry: (error, attempt) => {
      console.log(`Timeout attempt ${attempt} failed:`, error.message)
    }
  })
}

const data = await fetchWithTimeoutAndRetry('https://api.example.com/slow', 5000)
console.log('Data received:', data)

處理條件重試策略

typescript
import { retry } from 'radash'

async function smartRetry<T>(
  fn: () => Promise<T>,
  shouldRetry: (error: Error) => boolean
): Promise<T> {
  return retry(fn, {
    attempts: 3,
    delay: 1000,
    backoff: true,
    onRetry: (error, attempt) => {
      if (!shouldRetry(error)) {
        console.log('Error is not retryable:', error.message)
        throw error // 立即停止重試
      }
      console.log(`Retrying attempt ${attempt}:`, error.message)
    }
  })
}

// 只重試網絡錯誤,不重試驗證錯誤
const result = await smartRetry(
  async () => {
    const response = await fetch('https://api.example.com/data')
    if (!response.ok) {
      if (response.status === 400) {
        throw new Error('Validation error')
      } else {
        throw new Error('Network error')
      }
    }
    return response.json()
  },
  (error) => error.message === 'Network error'
)

console.log('Result:', result)

注意事項

  1. 錯誤處理: 只有拋出錯誤的操作才會重試
  2. 延遲策略: 可以使用固定延遲或指數退避
  3. 回調函數: 可以監聽重試事件進行日志記錄
  4. 性能: 重試會增加總執行時間
  5. 冪等性: 確保重試的操作是冪等的

與其他方法的區別

  • Promise.catch(): 只處理錯誤,不重試
  • retry(): radash提供的專門的重試工具
  • 手動重試: 需要編寫更多代碼

實際應用場景

  1. API調用: 處理網絡不穩定的情況
  2. 數據庫操作: 處理連接失敗
  3. 文件操作: 處理文件鎖定
  4. 第三方服務: 處理服務不可用
  5. 網絡請求: 處理超時和連接錯誤

Released under the MIT License.