async/await 本身无超时能力,需用 Promise.race 构造竞态或优先使用 AbortController;超时 Promise 必须 reject 错误,避免静默掩盖问题;fetch 应首选 AbortController 中止请求;封装 withTimeout 函数复用并区分错误类型处理。

async/await 本身不带超时能力,必须靠 Promise.race 主动构造竞态关系,让“原操作”和“超时信号”比谁先完成。关键不是能不能用,而是怎么用才不出错、不漏资源、不掩盖真实问题。
用 Promise.race 包装 await 目标
把原本 await 的 Promise,替换成 Promise.race([原始 Promise, 超时 Promise])。这样一旦超时 Promise 先 reject,整个 await 就会抛出错误,函数立即跳出等待。
- 超时 Promise 必须是
reject(new Error('Timeout')),不能 resolve 一个特殊值,否则 await 会静默拿到它,掩盖接口失败的真实原因 - 超时 Promise 内部的
setTimeout建议配套clearTimeout(显式清理更稳妥),避免旧环境潜在内存泄漏 - 不要在 race 外再套一层 try/catch 来“兜底”,那样会让超时错误和网络错误混在一起,难定位
优先对 fetch 使用 AbortController
如果目标是 fetch 请求,强烈推荐用 AbortController 替代 Promise.race。它能真正中止 HTTP 连接、释放 socket、停止数据接收;而 race 只是放弃等待,请求还在后台跑,浪费带宽和服务器资源。
- 创建
const controller = new AbortController(),把signal: controller.signal传给 fetch - 超时时调用
controller.abort(),fetch 会以error.name === 'AbortError'拒绝 Promise - 旧环境不支持 AbortController(如某些 Node.js 版本)时,再降级使用 race 方案
封装成可复用的 withTimeout 函数
避免每次手动写 race,抽成工具函数,语义清晰、便于加日志或监控:
- 函数接收原始 Promise 和毫秒数,返回新 Promise,不修改原 Promise,保证调用方控制权
- 错误信息里带上上下文,比如接口路径或参数摘要,方便排查“哪次调用卡住了”
- 不在 withTimeout 内做重试——那是业务逻辑,应由上层 async 函数根据错误类型决定是否重发
错误处理要分清来源,别一棍子打死
catch 到错误后,不能直接认为“流程失败”,得判断是不是超时:
- 检查
err.name === 'AbortError'(AbortController)或err.message.includes('Timeout')(race) - 超时场景常可降级:返回缓存数据、展示默认 UI、记录异常后继续执行后续步骤
- 网络错误(如 404、500)、解析失败等,才是需要中断或提示用户的真正异常

















