feat:代码优化与Mock数据清理
This commit is contained in:
167
frontend/src/utils/retry.ts
Normal file
167
frontend/src/utils/retry.ts
Normal file
@@ -0,0 +1,167 @@
|
||||
/**
|
||||
* 重试工具函数
|
||||
* @description 提供通用的重试机制,支持指数退避和自定义重试条件
|
||||
*/
|
||||
|
||||
/**
|
||||
* 重试选项配置
|
||||
*/
|
||||
export interface RetryOptions {
|
||||
/** 最大重试次数,默认 3 */
|
||||
maxRetries?: number
|
||||
/** 初始延迟时间(毫秒),默认 2000 */
|
||||
initialDelayMs?: number
|
||||
/** 是否使用指数退避,默认 true */
|
||||
useExponentialBackoff?: boolean
|
||||
/** 最大延迟时间(毫秒),默认 30000 */
|
||||
maxDelayMs?: number
|
||||
/** 自定义重试条件函数,返回 true 表示应该重试 */
|
||||
shouldRetry?: (error: any, attempt: number) => boolean
|
||||
/** 重试前的回调函数 */
|
||||
onRetry?: (error: any, attempt: number, delay: number) => void
|
||||
}
|
||||
|
||||
/**
|
||||
* 默认重试条件 - 检查是否是 rate limiting 错误
|
||||
* @param error 错误对象
|
||||
* @returns 是否应该重试
|
||||
*/
|
||||
export function defaultShouldRetry(error: any): boolean {
|
||||
if (!error) return false
|
||||
const message = error?.message || String(error)
|
||||
// 检查 406 Not Acceptable、429 Too Many Requests 等 rate limiting 错误
|
||||
const isRateLimit =
|
||||
message.includes('406') ||
|
||||
message.includes('429') ||
|
||||
message.includes('Not Acceptable') ||
|
||||
message.includes('Too Many Requests') ||
|
||||
message.includes('rate limit')
|
||||
return isRateLimit
|
||||
}
|
||||
|
||||
/**
|
||||
* 带重试机制的异步函数执行器
|
||||
*
|
||||
* @example
|
||||
* ```typescript
|
||||
* // 基本用法
|
||||
* const result = await withRetry(() => api.someRequest())
|
||||
*
|
||||
* // 自定义重试次数和延迟
|
||||
* const result = await withRetry(() => api.request(), {
|
||||
* maxRetries: 5,
|
||||
* initialDelayMs: 1000,
|
||||
* useExponentialBackoff: true
|
||||
* })
|
||||
*
|
||||
* // 自定义重试条件
|
||||
* const result = await withRetry(() => api.request(), {
|
||||
* shouldRetry: (error) => error?.code === 'NETWORK_ERROR'
|
||||
* })
|
||||
*
|
||||
* // 带重试回调
|
||||
* const result = await withRetry(() => api.request(), {
|
||||
* onRetry: (error, attempt, delay) => {
|
||||
* console.log(`第 ${attempt} 次重试,等待 ${delay}ms`, error.message)
|
||||
* }
|
||||
* })
|
||||
* ```
|
||||
*
|
||||
* @param fn - 要执行的异步函数
|
||||
* @param options - 重试选项配置
|
||||
* @returns Promise resolves with the result of the function
|
||||
* @throws 如果重试次数用尽,抛出最后一次的错误
|
||||
*/
|
||||
export async function withRetry<T>(
|
||||
fn: () => Promise<T>,
|
||||
options: RetryOptions = {},
|
||||
): Promise<T> {
|
||||
const {
|
||||
maxRetries = 3,
|
||||
initialDelayMs = 2000,
|
||||
useExponentialBackoff = true,
|
||||
maxDelayMs = 30000,
|
||||
shouldRetry = defaultShouldRetry,
|
||||
onRetry,
|
||||
} = options
|
||||
|
||||
let lastError: any = null
|
||||
let currentDelay = initialDelayMs
|
||||
|
||||
for (let attempt = 1; attempt <= maxRetries + 1; attempt++) {
|
||||
try {
|
||||
// 执行目标函数
|
||||
return await fn()
|
||||
} catch (error: any) {
|
||||
lastError = error
|
||||
|
||||
// 判断是否应该重试
|
||||
const shouldRetryAttempt = shouldRetry(error, attempt)
|
||||
|
||||
// 如果是最后一次尝试 或者 不应该重试,则抛出错误
|
||||
if (attempt > maxRetries || !shouldRetryAttempt) {
|
||||
if (attempt > maxRetries) {
|
||||
console.error(
|
||||
`❌ [withRetry] 已达到最大重试次数 (${maxRetries}),放弃请求`,
|
||||
)
|
||||
}
|
||||
throw error
|
||||
}
|
||||
|
||||
// 执行重试回调
|
||||
if (onRetry) {
|
||||
onRetry(error, attempt, currentDelay)
|
||||
}
|
||||
|
||||
// 打印日志
|
||||
console.log(
|
||||
`⏳ [withRetry] 第 ${attempt} 次重试,等待 ${currentDelay}ms...`,
|
||||
error?.message || String(error),
|
||||
)
|
||||
|
||||
// 等待延迟时间
|
||||
await new Promise((resolve) => setTimeout(resolve, currentDelay))
|
||||
|
||||
// 计算下一次延迟时间(指数退避)
|
||||
if (useExponentialBackoff) {
|
||||
currentDelay = Math.min(currentDelay * 2, maxDelayMs)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
//理论上不会到达这里,因为循环内会 throw
|
||||
throw lastError
|
||||
}
|
||||
|
||||
/**
|
||||
* 简化的重试装饰器 - 适用于类方法
|
||||
*
|
||||
* @example
|
||||
* ```typescript
|
||||
* class MyService {
|
||||
* @retryable({ maxRetries: 3, initialDelayMs: 1000 })
|
||||
* async fetchData() {
|
||||
* return await api.request()
|
||||
* }
|
||||
* }
|
||||
* ```
|
||||
*
|
||||
* @param options - 重试选项配置
|
||||
* @returns 装饰器函数
|
||||
*/
|
||||
export function retryable(options: RetryOptions = {}) {
|
||||
return function <T extends (...args: any[]) => Promise<any>>(
|
||||
_target: any,
|
||||
_propertyKey: string,
|
||||
descriptor: TypedPropertyDescriptor<T>,
|
||||
) {
|
||||
const originalMethod = descriptor.value
|
||||
if (!originalMethod) return descriptor
|
||||
|
||||
descriptor.value = function (this: any, ...args: Parameters<T>) {
|
||||
return withRetry(() => originalMethod.apply(this, args), options)
|
||||
} as T
|
||||
|
||||
return descriptor
|
||||
} as MethodDecorator
|
||||
}
|
||||
@@ -264,6 +264,24 @@ class WebSocketClient {
|
||||
get id(): string | undefined {
|
||||
return this.socket?.id
|
||||
}
|
||||
|
||||
/**
|
||||
* 监听事件
|
||||
*/
|
||||
on(event: string, callback: (...args: any[]) => void): void {
|
||||
if (this.socket) {
|
||||
this.socket.on(event, callback)
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 取消监听事件
|
||||
*/
|
||||
off(event: string, callback?: (...args: any[]) => void): void {
|
||||
if (this.socket) {
|
||||
this.socket.off(event, callback)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 导出单例
|
||||
|
||||
Reference in New Issue
Block a user