在处理多个异步请求时,一个常见难题在于:如何在一个try-catch块内精确区分每个接口的报错,分别进行针对性处理,同时确保其他正常流程不受影响。核心判断标准可归纳为——「错误类型可区分」「校验逻辑可复用」「异常流不打断正常流程」这三大关键要素。

使用单个try-catch包裹多个await调用,本身并不复杂;但若要实现对不同接口的报错精准识别、分类处理、互不干扰,核心就在于上述三点。
统一包装接口,让错误携带上下文标识
避免直接使用原始的fetch或axios请求进行await操作,而是封装一层处理逻辑:无论成功或失败,均让错误对象附带code、source(例如'user-api'、'order-api')以及可选的retryable字段。这样一来,每个错误都自带“身份标识”,后续识别将更加可靠。
- 例如,封装一个函数
callApi(url, options),在catch中抛出new Error(`[user-api] Network failed: ${err.message}`),并附加属性:err.source = 'user-api'; err.code = 'NETWORK_ERROR'; - 后端返回的业务错误(如400/401/500)也应统一转换为带有source和code的Error实例,避免依赖message字符串进行匹配——那种方式既脆弱又难以维护。
在 catch 内按 source + code 分支处理,避免 try-catch 嵌套堆叠
在单个try-catch中await多个接口后,catch捕获的是第一个抛出的错误。因此重点不在于“捕获哪个”,而在于“如何知道错误出自哪里”。推荐采用try...catch + 可控的Promise.allSettled或顺序await + 错误标记:
- 若需并发执行(如三个接口无依赖关系),使用
Promise.allSettled([p1, p2, p3]),结果数组中的每个item均包含status: 'fulfilled' | 'rejected',逐一检查即可,无需try-catch干预。 - 若需顺序执行(如第二步依赖第一步结果),则编写
await p1; await p2; await p3;,并在catch中根据error.source判断是哪一步出错,然后执行相应降级策略(例如p1失败则跳过p2与p3,p2失败则仍继续执行p3)。
定义错误策略映射表,解耦处理逻辑
将“什么错误 → 如何响应”抽离为配置对象,避免catch中堆满if-else判断:
- 例如:
const ERROR_HANDLERS = { 'user-api': { 'TOKEN_EXPIRED': handleTokenRefresh, 'NOT_FOUND': () => toast('用户不存在') } } - catch中只需执行:
if (err.source && ERROR_HANDLERS[err.source]?.[err.code]) { ERROR_HANDLERS[err.source][err.code](err); } - 如此,新增接口或错误码时,仅需修改映射表,无需改动主流程,维护成本显著降低。
允许部分失败继续执行,用结果容器收口
真正优雅之处在于:不将“所有接口必须成功”作为前提条件。定义一个结果结构,例如:{ user: { data: null, error: null }, order: { data: null, error: null } }。每个await均包裹在独立try-catch中,失败时填充error,成功时填充data——最终汇总判断哪些可用、哪些需要兜底处理。
- 示例片段:
const result = {}; try { result.user = await callApi('/user'); } catch (e) { result.user = { error: e }; } try { result.order = await callApi('/order'); } catch (e) { result.order = { error: e }; } // 后续逻辑根据 result.user.data 或 result.user.error 进行分支处理 - 这种方式既保持了单个try-catch结构的简洁性,又实现了“尽力而为”的效果,比全链路中断更符合真实业务场景。
虽然不复杂,但容易忽略的一点是:错误的可识别性,比错误的捕获时机更为重要。先让每个错误自带身份标识,再用结构化方式读取它,比层层嵌套try-catch更轻量、更稳定、更易于测试。
