当前位置:首页 > 文章列表 > 文章 > 前端 > JavaScript Promise.all 失败后为什么其他请求仍可能继续

JavaScript Promise.all 失败后为什么其他请求仍可能继续

来源:17golang原创 2026-09-09 20:10:23 0浏览 收藏

页面同时加载用户资料、权限和推荐列表时,Promise.all() 只会告诉你“这一组结果不能整体使用”,不会替你取消已经启动的请求。某一个 Promise 先 reject 后,其他 fetch 仍可能继续传输、解析,甚至在自己的回调里修改状态。要真正停止它们,必须把取消信号传给每个请求,再用批次编号挡住迟到响应。

官方地址:https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Promise/all

要点速览
  • Promise.all 的 fail-fast 是结果语义,不是资源取消机制。
  • 同一批任务共享一个 AbortSignal,失败、切页或重试时由批次统一触发 abort()
  • 提交 UI 状态前同时检查请求代次和取消状态,避免旧响应覆盖新数据。

先分清 Promise.all 的失败边界

Promise.all([a, b, c]) 在任意输入拒绝时立即拒绝,并把第一个拒绝原因交给调用方;这只是聚合 Promise 的状态变化。abc 早已分别开始执行,聚合失败不会撤回它们已经发出的网络请求,也不会自动终止定时器、流读取或自定义异步任务。

JavaScript Promise.all 聚合失败边界与三个仍在执行的 fetch 请求关系图
图1:Promise.all 只收敛聚合结果,底层请求仍需要独立的取消通道。

因此,看到“页面已经进入 catch,但网络面板还有请求完成”并不矛盾。先记录每个任务的开始、成功、失败和取消,再决定是否需要取消;如果只是想拿到所有结果而允许部分失败,应考虑 Promise.allSettled(),不要用取消去掩盖业务上的部分可用。

用共享 AbortController 让取消信号到达每个请求

对于可取消的 Web API,常见做法是一次批次创建一个控制器,把它的 signal 交给所有 fetch。任一请求发生不可继续的错误时,先调用 controller.abort(),再让外层等待聚合 Promise 的结果。示例中的注释说明了取消原因和清理边界:

async function loadDashboard(urls) {
  // 一个批次只创建一个控制器,让所有 fetch 共享取消信号。
  const controller = new AbortController();
  const { signal } = controller;

  const tasks = urls.map((url) =>
    fetch(url, { signal }).then((response) => {
      // HTTP 失败不会自动 reject,显式抛出便于 Promise.all 统一处理。
      if (!response.ok) throw new Error(`HTTP ${response.status}: ${url}`);
      return response.json();
    }),
  );

  try {
    return await Promise.all(tasks);
  } catch (error) {
    // 失败意味着本批结果不可用,协作取消仍未结束的请求。
    controller.abort();
    if (error.name === "AbortError") throw error;
    throw error;
  }
}

这里的关键不是把 AbortController 放进 Promise.all,而是把同一个 signal 传进每一个可取消操作。取消后通常会得到名称为 AbortError 的拒绝;它代表用户切页、主动重试或批次清理,不一定代表服务器故障。生产代码应把它和真正的 HTTP、解析、权限错误分开记录。

用请求代次挡住旧批次的迟到结果

abort() 是协作式通知,不能抹掉已经进入浏览器队列、缓存或业务回调的所有工作。页面快速切换筛选条件时,旧批次即使被取消,也可能在取消前完成。为每次加载分配递增的 generation,只有仍属于当前批次的结果才允许写入状态:

let currentGeneration = 0;
let activeController = null;

async function refreshPanel(filters, render) {
  // 新批次先取消旧批次,并用代次识别迟到结果。
  activeController?.abort();
  const controller = new AbortController();
  activeController = controller;
  const generation = ++currentGeneration;

  try {
    const result = await loadDashboard(buildUrls(filters), controller.signal);
    // 取消状态与代次都要检查,防止旧请求覆盖新界面。
    if (controller.signal.aborted || generation !== currentGeneration) return;
    render(result);
  } catch (error) {
    // 用户主动切换条件不应显示成红色业务错误。
    if (error.name !== "AbortError" && generation === currentGeneration) {
      render({ error });
    }
  } finally {
    // 只清理自己持有的控制器,避免误删新批次的引用。
    if (generation === currentGeneration) activeController = null;
  }
}

上例中 loadDashboard 需要改为接收外部 signal,或者由调用方直接创建任务;不要在内部无条件新建控制器,否则外层的取消信号无法传进去。对于非 fetch 的任务,则要在任务实现中主动监听 signal.aborted,并在合适的清理点停止工作。

按业务选择 all、allSettled 与可取消策略

选择组合器前先问一个问题:这组请求是“必须全部成功”还是“尽量收集可用结果”?前者可以使用 Promise.all,失败后通过共享信号结束剩余请求;后者使用 Promise.allSettled,让页面分别展示成功卡片和失败卡片,但仍可在用户离开页面时主动取消。

场景组合器取消策略验收信号
权限与主体数据缺一不可all关键错误触发批次 abortcatch 后不再提交整页结果
多个独立统计卡片allSettled切页或重试时 abort成功卡片保留,失败卡片可解释
新筛选覆盖旧筛选按依赖决定新代次先取消旧代次旧响应不能 render

排查时至少覆盖四个场景:一个请求立刻失败、一个请求慢速完成、用户连续切换两次筛选、组件卸载后仍有响应返回。检查网络层是否收到取消、日志是否把 AbortError 与服务端错误分开、状态层是否验证代次。只有“聚合失败、底层任务取消、旧结果不回写”三件事同时成立,才算完成了并发请求的取消设计。

JavaScript AbortController 将取消信号传给多个 fetch 并由请求代次保护界面的关系图
图2:共享 AbortSignal 配合请求代次,覆盖失败、切页和重试三条取消路径。

常见问题

Promise.all 失败后还能拿到其他请求的结果吗?

底层请求可能仍会完成,但聚合 Promise 不会再返回完整数组。若要逐项读取成功或失败,请改用 Promise.allSettled 或为每项单独处理。

调用 abort() 后服务器一定停止处理吗?

不一定。它主要通知浏览器和客户端停止等待或传输,服务器已接收的请求可能已经进入处理流程,服务端仍需自己的取消或幂等设计。

为什么用了 AbortController 还会出现旧数据?

取消是异步协作,响应可能在取消前完成。提交状态前增加批次编号或请求 token 检查,才能拦住迟到结果。

Promise.all 看成“结果聚合器”,把 AbortController 看成“取消通道”,再用代次检查保护 UI,三者职责分开后,并发请求的失败、重试和切页行为就更容易解释和测试。

版本声明
本文转载于:17golang原创 如有侵犯,请联系study_golang@163.com删除
Go encoding/xml 命名空间前缀变化时怎么按 URI 判断Go encoding/xml 命名空间前缀变化时怎么按 URI 判断
上一篇
Go encoding/xml 命名空间前缀变化时怎么按 URI 判断
Go regexp 编译正则失败时怎么把错误交给配置层
下一篇
Go regexp 编译正则失败时怎么把错误交给配置层
查看更多
最新文章
查看更多
课程推荐
  • 前端进阶之JavaScript设计模式
    前端进阶之JavaScript设计模式
    设计模式是开发人员在软件开发过程中面临一般问题时的解决方案,代表了最佳的实践。本课程的主打内容包括JS常见设计模式以及具体应用场景,打造一站式知识长龙服务,适合有JS基础的同学学习。
    543次学习
  • GO语言核心编程课程
    GO语言核心编程课程
    本课程采用真实案例,全面具体可落地,从理论到实践,一步一步将GO核心编程技术、编程思想、底层实现融会贯通,使学习者贴近时代脉搏,做IT互联网时代的弄潮儿。
    516次学习
  • 简单聊聊mysql8与网络通信
    简单聊聊mysql8与网络通信
    如有问题加微信:Le-studyg;在课程中,我们将首先介绍MySQL8的新特性,包括性能优化、安全增强、新数据类型等,帮助学生快速熟悉MySQL8的最新功能。接着,我们将深入解析MySQL的网络通信机制,包括协议、连接管理、数据传输等,让
    500次学习
  • JavaScript正则表达式基础与实战
    JavaScript正则表达式基础与实战
    在任何一门编程语言中,正则表达式,都是一项重要的知识,它提供了高效的字符串匹配与捕获机制,可以极大的简化程序设计。
    487次学习
  • 从零制作响应式网站—Grid布局
    从零制作响应式网站—Grid布局
    本系列教程将展示从零制作一个假想的网络科技公司官网,分为导航,轮播,关于我们,成功案例,服务流程,团队介绍,数据部分,公司动态,底部信息等内容区块。网站整体采用CSSGrid布局,支持响应式,有流畅过渡和展现动画。
    485次学习
查看更多
AI推荐
  • H2O EvalGPT:开源LLM大模型评估与排行榜工具
    H2O EvalGPT
    H2O EvalGPT是H2O.ai推出的开源LLM评估平台,提供详细的大模型性能排行榜、行业特定基准测试及A/B测试功能,助您快速选择最适合项目的高性能大语言模型。
    51次使用
  • SuperCLUE中文大模型评测基准:功能、能力维度与应用指南
    SuperCLUE
    SuperCLUE是权威的中文大语言模型综合评测基准,涵盖语言理解、知识应用、AI Agent智能体及安全性等12项核心能力。通过多轮对话与客观测试,定期发布榜单与技术报告,为模型研发、优化及行业选型提供科学依据。
    201次使用
  • C-Eval中文评测基准:大语言模型多学科能力评估指南
    C-Eval
    深入了解C-Eval中文评估套件,涵盖52个学科与4级难度。本文详解其功能特点、Zero-shot/Few-shot使用方法及代码示例,助您全面评测LLM中文理解与泛化能力。
    137次使用
  • AI Prompt Library:免费AI提示词库,助力ChatGPT高效创作与营销
    AI Prompt Library
    探索AI Prompt Library免费资源库,涵盖营销、写作及多场景AI提示词。兼容ChatGPT、Claude等工具,一键复制优化输出,提升工作效率。
    68次使用
  • Generrated:DALL·E 2/3 AI绘画提示词灵感库与图像对比平台
    Generrated
    Generrated汇集9300+张DALL·E生成图像及对应提示词,支持查看完整图集、对比DALL·E 2与3版本差异,是AI绘图新手学习Prompt设计与获取创作灵感的实用工具。
    49次使用