📄 retry.ts  •  6807 bytes
/**
 * 容错系统 - 重试执行器
 * Phase 4: 智能重试 + 指数退避
 */
import { 
  type RetryConfig, 
  type RetryState, 
  type RetryHistoryItem,
  type RetryStrategy,
  type ExecutionResult,
  type FallbackStrategy,
  DEFAULT_RETRY_CONFIG 
} from './types'
import { isRetryable, classifyError } from './classifier'

/**
 * 计算重试延迟
 */
export function calculateDelay(
  attempt: number,
  config: RetryConfig
): number {
  let delay: number
  
  switch (config.strategy) {
    case 'immediate':
      delay = 0
      break
    
    case 'linear':
      delay = config.initialDelay * attempt
      break
    
    case 'exponential':
      delay = config.initialDelay * Math.pow(config.backoffMultiplier, attempt - 1)
      break
    
    case 'fibonacci':
      // 斐波那契数列
      const fib = [1, 1, 2, 3, 5, 8, 13, 21, 34, 55]
      const fibIndex = Math.min(attempt - 1, fib.length - 1)
      delay = config.initialDelay * fib[fibIndex]
      break
    
    default:
      delay = config.initialDelay * Math.pow(config.backoffMultiplier, attempt - 1)
  }
  
  // 限制最大延迟
  delay = Math.min(delay, config.maxDelay)
  
  // 添加随机抖动
  if (config.jitter) {
    const jitterAmount = delay * 0.2
    delay = delay + (Math.random() * jitterAmount * 2 - jitterAmount)
  }
  
  return Math.floor(delay)
}

/**
 * 创建初始重试状态
 */
export function createRetryState(): RetryState {
  return {
    attempt: 0,
    delay: 0,
    history: [],
  }
}

/**
 * 执行带重试的操作
 */
export async function withRetry<T>(
  operation: () => Promise<T>,
  config: Partial<RetryConfig> = {},
  onRetry?: (state: RetryState, error: Error) => void
): Promise<ExecutionResult<T>> {
  const startTime = Date.now()
  const retryConfig: RetryConfig = { ...DEFAULT_RETRY_CONFIG, ...config }
  const state = createRetryState()
  
  if (!retryConfig.enabled) {
    // 不启用重试,直接执行
    try {
      const data = await operation()
      return {
        success: true,
        data,
        attempts: 1,
        duration: Date.now() - startTime,
        retries: [],
      }
    } catch (error: any) {
      return {
        success: false,
        error: classifyError(error),
        attempts: 1,
        duration: Date.now() - startTime,
        retries: [],
      }
    }
  }
  
  while (state.attempt < retryConfig.maxAttempts) {
    state.attempt++
    const attemptStart = Date.now()
    
    try {
      const data = await operation()
      
      return {
        success: true,
        data,
        attempts: state.attempt,
        duration: Date.now() - startTime,
        retries: state.history,
      }
    } catch (error: any) {
      const duration = Date.now() - attemptStart
      const errorClassification = classifyError(error)
      
      // 记录重试历史
      state.history.push({
        attempt: state.attempt,
        timestamp: Date.now(),
        error: errorClassification.message,
        duration,
      })
      
      // 检查是否应该重试
      const shouldRetry = 
        state.attempt < retryConfig.maxAttempts &&
        isRetryable(error)
      
      if (!shouldRetry) {
        return {
          success: false,
          error: errorClassification,
          attempts: state.attempt,
          duration: Date.now() - startTime,
          retries: state.history,
        }
      }
      
      // 计算延迟并等待
      state.delay = calculateDelay(state.attempt, retryConfig)
      state.lastError = errorClassification.message
      state.nextRetryTime = Date.now() + state.delay
      
      // 回调
      if (onRetry) {
        onRetry(state, error)
      }
      
      // 等待
      if (state.delay > 0) {
        await sleep(state.delay)
      }
    }
  }
  
  // 超过最大重试次数
  return {
    success: false,
    error: classifyError('max retries exceeded'),
    attempts: state.attempt,
    duration: Date.now() - startTime,
    retries: state.history,
  }
}

/**
 * 执行带降级的操作
 */
export async function withFallback<T>(
  primary: () => Promise<T>,
  fallbacks: FallbackStrategy<T>[],
  config: Partial<RetryConfig> = {}
): Promise<ExecutionResult<T>> {
  // 尝试主操作
  const result = await withRetry(primary, config)
  
  if (result.success) {
    return result
  }
  
  // 尝试降级策略
  for (const fallback of fallbacks) {
    if (fallback.condition && !fallback.condition(result.error! as any)) {
      continue
    }
    
    try {
      const fallbackResult = await fallback.handler()
      return {
        success: true,
        data: fallbackResult,
        attempts: result.attempts + 1,
        duration: Date.now() - result.duration,
        retries: result.retries,
      }
    } catch {
      // 继续下一个降级策略
    }
  }
  
  // 所有策略都失败
  return result
}

/**
 * 并行执行带重试
 */
export async function withRetryParallel<T>(
  operations: (() => Promise<T>)[],
  config: Partial<RetryConfig> = {}
): Promise<ExecutionResult<T>[]> {
  return Promise.all(
    operations.map(op => withRetry(op, config))
  )
}

/**
 * 带超时的执行
 */
export async function withTimeout<T>(
  operation: () => Promise<T>,
  timeoutMs: number,
  timeoutError?: string
): Promise<T> {
  return Promise.race([
    operation(),
    sleep(timeoutMs).then(() => {
      throw new Error(timeoutError || `操作超时 (${timeoutMs}ms)`)
    }),
  ])
}

/**
 * 睡眠辅助函数
 */
function sleep(ms: number): Promise<void> {
  return new Promise(resolve => setTimeout(resolve, ms))
}

/**
 * 格式化重试状态
 */
export function formatRetryState(state: RetryState): string {
  let output = `  🔄 重试状态:\n`
  output += `  - 尝试次数: ${state.attempt}\n`
  output += `  - 下次延迟: ${state.delay}ms\n`
  
  if (state.lastError) {
    output += `  - 上次错误: ${state.lastError}\n`
  }
  
  if (state.nextRetryTime) {
    const remaining = Math.max(0, state.nextRetryTime - Date.now())
    output += `  - 剩余等待: ${remaining}ms\n`
  }
  
  if (state.history.length > 0) {
    output += `\n  📜 重试历史:\n`
    for (const item of state.history.slice(-3)) {
      output += `    #${item.attempt}: ${item.error} (${item.duration}ms)\n`
    }
  }
  
  return output
}

/**
 * 获取重试统计
 */
export function getRetryStats(results: ExecutionResult[]): {
  total: number
  success: number
  failed: number
  totalAttempts: number
  avgAttempts: number
} {
  const success = results.filter(r => r.success).length
  const totalAttempts = results.reduce((sum, r) => sum + r.attempts, 0)
  
  return {
    total: results.length,
    success,
    failed: results.length - success,
    totalAttempts,
    avgAttempts: results.length > 0 ? totalAttempts / results.length : 0,
  }
}