Files
Aether/frontend/src/utils/importRetry.ts
T
fawney19 cf2eee222e refactor: 前端全面替换 any 为 unknown 并统一错误处理,后端用量记录补写请求头/体
- 前端 API 层、stores、conversation 解析器、组件全面替换 any 为 unknown/具体类型
- 错误处理统一使用 parseApiError/getErrorStatus 替代 err.response?.data?.detail 模式
- 后端 handler/TaskService/UsageLifecycle/StreamTracker 链路传递 request_headers/request_body
- streaming/pending 状态更新时可补写客户端和提供商的请求头及请求体
- 新增 TaskService 和 UsageService 相关测试
2026-02-22 00:43:41 +08:00

116 lines
3.3 KiB
TypeScript

/**
* 带重试机制和缓存处理的动态导入工具
*/
const MAX_RETRIES = 3
const RETRY_DELAY = 1000 // 1秒
const CACHE_BUSTER_DELAY = 2000 // 2秒后尝试缓存清除
// 模块缓存
const moduleCache = new Map<string, Promise<unknown>>()
/**
* 清除浏览器缓存的工具函数
*/
function clearBrowserCache() {
if (typeof window !== 'undefined') {
// 清除一些可能的缓存
if ('caches' in window) {
caches.keys().then(names => {
names.forEach(name => {
caches.delete(name)
})
})
}
}
}
/**
* 检查错误是否是网络/缓存相关
*/
function isNetworkOrCacheError(error: unknown): boolean {
const err = error as { message?: string; name?: string } | null
const errorMessage = err?.message || ''
return (
errorMessage.includes('Failed to fetch') ||
errorMessage.includes('Loading chunk') ||
errorMessage.includes('dynamically imported module') ||
errorMessage.includes('NetworkError') ||
err?.name === 'ChunkLoadError'
)
}
/**
* 重试动态导入
* @param importFn 动态导入函数
* @param retries 剩余重试次数
* @param cacheKey 缓存键
* @returns Promise
*/
export async function importWithRetry<T = unknown>(
importFn: () => Promise<T>,
retries: number = MAX_RETRIES,
cacheKey?: string
): Promise<T> {
try {
// 如果有缓存键且缓存中存在,直接返回
if (cacheKey && moduleCache.has(cacheKey)) {
return await moduleCache.get(cacheKey) as T
}
const importPromise = importFn()
// 缓存 Promise
if (cacheKey) {
moduleCache.set(cacheKey, importPromise)
}
const result = await importPromise
return result
} catch (error) {
// 如果是缓存相关错误,清除对应缓存
if (cacheKey && moduleCache.has(cacheKey)) {
moduleCache.delete(cacheKey)
}
if (retries > 0 && isNetworkOrCacheError(error)) {
// 如果是第二次重试,尝试清除浏览器缓存
if (MAX_RETRIES - retries + 1 === 2) {
clearBrowserCache()
await new Promise(resolve => setTimeout(resolve, CACHE_BUSTER_DELAY))
} else {
await new Promise(resolve => setTimeout(resolve, RETRY_DELAY))
}
return importWithRetry(importFn, retries - 1, cacheKey)
} else {
// 最后的fallback:如果是网络/缓存错误,刷新页面
if (isNetworkOrCacheError(error) && typeof window !== 'undefined') {
// 添加一个时间戳参数来强制刷新
const url = new URL(window.location.href)
url.searchParams.set('_t', Date.now().toString())
window.location.href = url.toString()
}
throw error
}
}
}
/**
* 创建带重试的组件导入函数
* @param importPath 组件路径
* @returns 组件导入函数
*/
export function createRetryableImport(importPath: string) {
const cacheKey = importPath
return () => importWithRetry(() => import(/* @vite-ignore */ importPath), MAX_RETRIES, cacheKey)
}
/**
* 预加载关键模块
*/
export function preloadCriticalModules() {
// 在开发环境中预加载已被禁用,因为路径别名在运行时动态导入中不可用
// 模块会在需要时按需加载,这在开发环境中是可接受的
// 生产环境中模块已经被构建和优化,不需要预加载
}