async/await并发请求异常处理方法

作者:技术拾光时间:2026-07-25 08:32:19浏览:327

用Promise.allSettled替代Promise.all以保留所有请求结果,对每个fetch手动检查响应状态并抛出异常,控制并发数量并支持AbortSignal取消请求,对瞬态错误采用指数退避重试并设置降级方案,从而提升系统容错能力。

在处理 async/await 并发请求的异常时,最常见的误区是“要么全成功才继续,要么一错就中断”。真正的关键在于让系统具备容错能力——区分失败类型、保留部分结果、可控重试,同时避免未卸载组件的副作用。下面从几个核心环节展开。

async/await并发请求异常处理方法

使用 Promise.allSettled 替代 Promise.all

为什么不要死磕 Promise.all?它的毛病是“一拒即停”——只要有一个请求失败,其他 pending 请求的结果全被丢弃,而且你无法获取它们。而 Promise.allSettled 更为可靠,它会等待所有请求出结果,然后返回一个数组,每个对象包含 status("fulfilled" 或 "rejected")、value(成功值)或 reason(错误原因)。

  • 特别适合“尽力而为”的场景:批量提交表单、并行拉取用户资料、聚合监控指标。不需要因为某个模块挂掉就放弃整个页面。
  • 示例:
    const results = await Promise.allSettled([fetch('/user/1'), fetch('/user/2')]);
    results.forEach((r, i) => {
    if (r.status === 'fulfilled') console.log(`第${i+1}个成功`, r.value);
    else console.warn(`第${i+1}个失败`, r.reason);
    });

对每个 fetch 做响应体校验

有一个常见陷阱:HTTP 状态码不是 2xx 时,fetch 并不会自动 reject。它只在网络层出错(如 DNS 解析失败、连接被拒绝)时才会抛出异常,而 401、500、404 等都会正常 resolve。这导致后续调用 .json() 时可能直接报错,或拿到空数据引发连锁崩溃。

  • 解决办法:在 await fetch() 之后立即检查 res.ok,如果为 false 就手动 throw 一个错误。这样业务逻辑就能统一捕获。
  • 可以封装成一个通用函数:async function safeFetch(url) { const res = await fetch(url); if (!res.ok) throw new Error(`${res.status} ${res.statusText}`); return res; }
  • 配合 allSettled 使用,网络错误、服务端错误、权限错误全都会被收进 reason 字段,方便分类统计和后续处理。

控制并发数量并支持取消请求

假设一口气往 allSettled 里丢 100 个请求,后端可能直接被压垮,CDN 也可能触发限流。更严重的是,用户切换页面后,那些还在运行的请求可能触发 setState 报错,造成内存泄漏。

  • 推荐做法:使用 Promise.mapLimit(如 p-map 库)或手写分批执行,每次最多并发 6 个。这样既不会压垮后端,也不会让浏览器卡死。
  • 同时,为每个 fetch 传入 AbortSignal,在组件卸载时(React 的 useEffect cleanup,Vue 的 onBeforeUnmount)调用 controller.abort() 取消请求。
  • 示例:
    const controller = new AbortController();
    await fetch(url, { signal: controller.signal });
    // 卸载时调用 controller.abort()

按需重试与降级策略

不是所有失败都值得重试。DNS 解析失败重试十次也是徒劳;超时可以用指数退避重试两三次;404 直接放弃即可。

  • 对 transient 错误(如 NetworkError、503、timeout)做指数退避,最多试 2–3 次。退避时间不要写死,可以用 Math.pow(2, retryCount) 等算法。
  • 给关键请求设置 fallback:头像加载失败显示默认图标,配置项获取失败使用 localStorage 的缓存值。这样用户体验不会因一个接口报错而崩溃。
  • 注意:不要在 allSettled 的回调里嵌套 await 重试——这会破坏并发性。最好在发起请求前决定是否重试,或直接使用 retry-axios 等成熟封装。