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.