用户反馈"页面一直转圈"。排查发现:导出报表接口响应 20s,但前端统一超时 15s,直接断了。
加大超时?简单但粗暴------所有接口都变成 30s 超时,普通查询也要等 30s 才报错,体验更差。我让 AI 帮我设计了一个动态超时策略,根据接口类型自动适配。
问题分析
接口类型 实际响应时间 统一 15s 超时的问题
────────── ────────── ──────────────────
普通查询 200ms-2s 正常
列表分页 500ms-5s 正常
复杂查询 3s-8s 正常
导出(小数据量) 5s-15s 偶尔超时
导出(大数据量) 15s-60s 必然超时 ❌
文件上传 10s-120s 必然超时 ❌
批量操作 5s-30s 经常超时 ❌
不同接口需要不同的超时时间,但又不想每个接口调用时手动传。
AI 设计的方案:3 层超时策略
yaml
优先级(高→低):
Layer 1: 接口级覆盖(单个接口指定)
↓ 没指定则走
Layer 2: 场景级默认(按 API 类型分类)
↓ 没匹配则走
Layer 3: 全局默认(15s)
typescript
// 超时策略配置
const TIMEOUT_STRATEGY = {
// 全局默认
default: 15_000,
// 场景级
scenes: {
query: 15_000, // 普通查询
list: 20_000, // 列表请求
mutation: 20_000, // 增删改
export: 120_000, // 导出
upload: 180_000, // 上传
batch: 60_000, // 批量操作
},
// 接口级覆盖(特殊接口)
overrides: {
'/report/export/full': 300_000, // 全量报表,5min
'/goods/batch-import': 180_000, // 商品批量导入
},
}
核心实现:请求拦截器
typescript
const getTimeout = (config: AxiosRequestConfig): number => {
const url = config.url || ''
// Layer 1: 接口级覆盖(config 里手动传的)
if (config.timeout) return config.timeout
// Layer 2: 接口路径匹配
const override = Object.entries(TIMEOUT_STRATEGY.overrides)
.find(([path]) => url.includes(path))
if (override) return override[1]
// Layer 3: 场景推断
const scene = inferScene(config)
return TIMEOUT_STRATEGY.scenes[scene] || TIMEOUT_STRATEGY.default
}
const inferScene = (config: AxiosRequestConfig): string => {
const { url = '', method, responseType } = config
// 导出类(responseType 为 blob 或 URL 包含 export)
if (responseType === 'blob' || url.includes('export')) return 'export'
// 上传类(Content-Type 包含 multipart)
if (config.headers?.['Content-Type']?.includes('multipart')) return 'upload'
// 批量操作(URL 包含 batch)
if (url.includes('batch')) return 'batch'
// 列表类(URL 包含 list/page 且为 GET)
if (method === 'get' && /\/(list|page)/.test(url)) return 'list'
// 增删改
if (['post', 'put', 'delete'].includes(method || '')) return 'mutation'
// 默认查询
return 'query'
}
关键设计:不需要改任何现有代码。 现有的接口调用完全不用动,拦截器自动推断超时时间。
使用方式
typescript
// 普通使用------自动推断(大部分场景)
export const getStoreList = (params) =>
request.get('/store/list', { params })
// → 自动匹配 list 场景,20s 超时
// 导出------自动识别
export const exportReport = (params) =>
request.get('/report/export', { params, responseType: 'blob' })
// → 自动匹配 export 场景,120s 超时
// 特殊接口------手动覆盖
export const exportFullReport = (params) =>
request.get('/report/export/full', { params, responseType: 'blob', timeout: 300_000 })
// → Layer 1 手动指定,5min
AI 帮我发现的额外优化点
对话过程中,AI 还建议了两个我没想到的优化:
1. 超时 ≠ 报错,给用户进度感知
typescript
// 对于已知耗时长的操作,显示进度提示
const exportWithProgress = async (params) => {
const hide = message.loading('正在生成报表,数据量较大请耐心等待...', 0)
try {
return await request.get('/report/export', {
params,
responseType: 'blob',
})
} finally {
hide()
}
}
2. 超时后的重试策略不同
typescript
// 普通查询超时:静默重试 1 次
// 导出超时:提示用户"数据量大,是否继续等待?"
// 上传超时:不重试,提示检查网络
const handleTimeout = (config: AxiosRequestConfig, error: AxiosError) => {
const scene = inferScene(config)
switch (scene) {
case 'query':
case 'list':
return retryOnce(config) // 静默重试
case 'export':
return confirmRetry('报表生成中,是否继续等待?', config, 300_000)
case 'upload':
message.error('上传超时,请检查网络后重试')
return Promise.reject(error)
default:
message.error('请求超时,请稍后重试')
return Promise.reject(error)
}
}
效果
erlang
优化前:
├── 导出接口:超时率 35%(用户点击后无响应 → 以为卡死 → 重复点击)
├── 批量操作:超时率 15%
├── 用户投诉:每周 8-10 条"页面卡死"
优化后:
├── 导出接口:超时率 2%(剩余是真的后端慢需要优化)
├── 批量操作:超时率 0%
├── 用户投诉:每周 3-4 条(减少 60%)
├── 额外收益:定位到 5 个真正需要后端优化的接口
什么时候需要动态超时
arduino
需要的信号:
├── 有导出/上传等长耗时接口
├── 经常收到"超时"报错
├── 不同接口耗时差异大(200ms vs 60s)
└── 统一超时值怎么调都不合适
不需要的:
├── 所有接口 <5s 就能响应
├── 后端保证响应时间 SLA
└── 项目小,接口少于 20 个
💬 你们项目的接口超时是怎么设置的?有没有遇到过"超时导致体验差"的问题?
🔗 完整 Skills 源码已开源 :github.com/sleepyccat/...,欢迎 Star ⭐ 和 PR。