📄 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,
}
}