async 封装异步请求的核心是统一错误处理:将 HTTP 状态校验、JSON 解析、业务码判断全置于 try 块内;传函数而非 Promise 以确保执行可控;保留原始错误类型便于分场景处理;catch 后须抛出原错误,避免吞掉异常。

用 async 封装异步数据请求,核心不是让代码“变异步”,而是把错误检查逻辑收拢、前置、可复用。关键在于把 HTTP 状态校验、响应体解析、业务码判断这些容易分散写的步骤,统一放进 try 块里,确保任何一环出错都能被同一处捕获和处理。
把校验逻辑全塞进 try 块里
很多人只 catch 网络异常,却忘了 fetch 成功返回 404/500 时 res.ok 是 false,或者后端返回 { code: 1, message: "token 过期" } 这类业务错误。这些都该在 try 内处理,而不是等调用方自己判。
- 先 await 获取响应,立刻检查
res.ok - 再
await res.json(),紧接着验证data.code === 0 - 所有中间步骤都不单独 try/catch,避免错误被吞或处理割裂
传函数而非 Promise,避免执行时机失控
封装函数接收的是一个“能返回 Promise 的函数”,比如 () => fetch('/api/user'),而不是直接传 fetch('/api/user')。这样能确保每次调用都是全新发起的请求,也方便在内部统一控制执行流和错误边界。
- 如果直接传已执行的 Promise,它会在封装函数定义时就运行,错误根本进不了你的
try - 传函数还能支持动态参数,比如
() => api.get('/user', { id: userId }) - 便于后续加日志、埋点、重试等横切逻辑
保留原始错误类型,别一律转成字符串
catch 到错误后,不要简单 throw new Error('请求失败')。原始错误可能是 TypeError(网络中断)、AbortError(超时取消)、或自定义业务错误。保留类型和堆栈,对定位问题和分场景处理(比如重试 vs 提示用户)至关重要。
- 可以加一层判断:如果是
TypeError,大概率是网络问题,可考虑自动重试 - 如果是业务错误(如
data.code !== 0),直接提示data.message - 打日志时用
console.error('[API]', err),别只打err.message
错误要透出,别在封装层吞掉
封装函数 catch 住错误后,该上报就上报,该提示就提示,但最后仍要 throw err。否则调用方无法用 await xxx().catch(...) 或外层 try/catch 做进一步处理,等于把控制权锁死了。
- 统一打日志或触发全局提示,属于副作用,不影响主流程
- 抛出原错误,让上层决定是重试、降级、还是展示给用户
- 如果某些场景确实需要静默失败,应作为可选参数显式传入,而非默认行为

















