2026年了,一页同时拉四个接口,挂了一个就整页白屏,这算 bug 吗?
结论先行:同一页数据用 Promise.all 并发拉取时,任意一个接口 rejected,整组立刻失败,已经拿到的成功结果会被一起丢弃,这是 Promise 规范定义的行为,不是 bug。2026 年在项目交付里真正要判断的是失败语义:这批数据是必须全都有,还是有几条算几条。前者保留 Promise.all 但要在内层兜底,后者换成 allSettled 更省事。
为什么挂一个接口,整组一起失败
Promise.all 内部只认两个信号:全部 fulfilled 就 resolve 一个结果数组;任意一个 rejected 就立刻 reject,不再等剩下的请求。这套语义在「要么全有、要么全无」的场景里是对的,但很多首屏数据其实是可降级展示的。
造成白屏的往往不是语义本身,而是调用方默认它一定会成功。用户未登录、接口限流、某个下游服务超时,都会让整页数据一起空掉,用户看到的是空页面而不是部分内容。
- 短路但不取消:任意一个 rejected 后立刻进失败分支,其他已发出的请求不会自动中止。
- 成功值被丢弃:reject 时外层只能拿到那一个错误,拿不到已成功的部分数据。
- 定位偏慢:错误对象里通常没有「是哪个接口挂的」这层信息,除非自己包一层标记。
- 加载态容易卡死:loading 写在 then 里而不是 finally 里,失败路径就永远清不掉。
四种并发写法对比(经验区间)
与其背 API,不如先给每个请求标上失败语义,再选聚合方式。下面按 2026 年常见项目用法整理,耗时与改造量都是经验区间,用于判断量级而非精确承诺。
- all 加内层 catch:适合首屏固定顺序的并行请求;改动点少、风险低,单个页面常见半天内能改完。
- allSettled 加结果过滤:适合报表、聚合列表这类「有几条算几条」的场景;每条结果保留 status 与 reason,失败可追溯,做错误上报更顺。
- 逐个串行 await:只在有前后依赖时使用;把 3~8 个并发接口改成串行后,总耗时常见从几百毫秒涨到 1~2 秒(经验区间)。
- any / race:多源取其一、超时竞速时用,日常业务页面出现频率较低,不建议为了写法新而套用。
再按改造代价看一组可核对对比:all 加内层 catch 属于低风险小改,缺点是失败原因要自己拼;allSettled 加后置过滤改动点多一点,换来的是失败原因可保留、可统计。两者在同一个页面里的回归工作量,常见相差半天左右(经验区间)。
先定失败语义,再选写法,最后补超时
顺序反过来做,就容易演变成「每个请求都糊一层 try/catch」,代码更难维护。
- 标注失败语义:把同页并发请求分成关键与可缺两类,写进接口对接说明或注释里,联调时双方认同一份。
- 再选聚合方式:关键的进 all 并各自包兜底值,注意别用 undefined 当兜底,后面解构容易再报一次错;可缺的进 allSettled。
- 统一错误出口:只在最外层 catch 一次,做降级提示与上报,避免多层 catch 互相吞掉,告警里分不清是聚合失败还是单点失败。
- 补超时与取消:用 AbortController 或请求库的 signal 给每个请求加超时,防止某个请求悬挂导致整组迟迟不返回、加载态一直转。
验收口径可以按这三条核对:能列出每个并发请求属于关键还是可缺;全部成功、部分失败、全部失败、超时四种情况都手工验过;白屏、loading 卡死、错误提示反复弹这三类问题在回归清单里确认不再出现。
交付现场:一次 401 拖垮整页的返工
一个首页要并行拉推荐位、购物车角标、个人资料、消息未读数四个接口,开发周期给了两周,前期赶进度直接写了裸的 Promise.all。上线后未登录用户的消息未读数返回 401 直接 reject,首页整块数据全空,被当成「页面坏了」报上来。返工做法是把购物车角标和消息未读数移出 all、改成 allSettled 后按 status 过滤,推荐位和个人资料保留 all 并各自包 catch。改造加回归大约多花半天到一天(经验区间),代价是当期另一个小需求顺延,但 401、限流、超时这几类场景不再白屏。
另一个容易忽略的点是抓取:如果首屏主体内容依赖这组必须全成功的请求,一旦整组失败,服务端渲染出的 HTML 可能近似空页,搜索抓取到的内容也会受影响。可结合渲染方案与搜索引擎官方文档核对,把关键内容做成可降级或直出,不要全押在一组必须全成功的请求上。
适用与不适用边界
适合动手改的情况:页面数据可以部分展示、并发接口在 3 个以上、并且能明确区分关键与可缺。这类场景做失败语义分层,收益通常立竿见影,回归成本也可控。
不必上重方案的情况:整页只调一个接口,或这组数据本来就必须同时成立(例如下单前的库存与价格校验),继续用 all 加一层 catch 就够,硬套 allSettled 只会让分支更多、可读性更差。
- 适合:首屏多接口并行、聚合列表、报表类页面、弱网或未登录时常态需要降级的场景。
- 不适合:强一致的校验链、单接口调用、有严格前后依赖的串行流程、对数据完整性不能打折的结算页。
常见问题
Promise.all 里一个失败,其他请求还会继续发吗?
会。all 只是不再等待它们,已发出的请求不会自动取消;要真正止损,需要配合 AbortController 或请求库的 signal 主动中断。
Promise.allSettled 和 Promise.all 该选哪个?
看失败语义。数据全都要就用 all 并在内层兜底;有几条算几条就用 allSettled,之后按 status 过滤。
用 try/catch 包住 Promise.all,能拿到部分成功的结果吗?
拿不到。reject 时成功值已被丢弃,想保留部分结果只能换成 allSettled,或给每个请求单独包 catch。
小程序基础库或老 WebView 不支持 allSettled 怎么办?
先按对应平台官方文档核对支持范围;确实不支持时,可用 all 加内层 catch 包装出等价效果,或按需引入 polyfill。
如果页面还在用裸的 Promise.all 拉多个接口,建议先花半小时给每个请求标上关键或可缺,再决定是否改成 allSettled 或内层兜底,并补上超时与取消。并发接口少于 3 个、或数据必须同时成立的项目,保持现状加一层 catch 即可,不必为追新写法而重构。
-
z-index 调到很大还是被盖住,2026年继续往上加有用吗?
日期:2026年9月16日 阅读:90
-
2026年了,大模型流式对话点了停止还在吐字,是接口没断还是前端没收口?
日期:2026年9月15日 阅读:100
-
2026年了,需求评审都点头了,为什么联调时还在补空状态和加载态?
日期:2026年9月14日 阅读:86
-
CDN 刷新过了,为什么还有用户看到旧页面?
日期:2026年9月13日 阅读:86
-
Uni-app 同时发小程序和 App,平台差异越堆越多,2026 年从哪一步开始收?
日期:2026年9月12日 阅读:86




