前端怎样合并多个取消信号
来源:17golang原创
时间:2026-10-18 02:34:45 490浏览 收藏
前端合并多个取消来源,可以把各个 AbortSignal 传给 AbortSignal.any([...]),再把返回的组合信号交给 fetch。任意来源取消,组合信号就会取消;这不是等待所有来源,也不是把多个 AbortController 合成一个能随意重置的控制器。
官方规范地址:https://dom.spec.whatwg.org/;API 说明:https://developer.mozilla.org/en-US/docs/Web/API/AbortSignal/any_static。下文只讨论联合取消,不把它当成权限控制、服务端事务回滚或旧浏览器通用兼容库。代码未在本机执行。
需要保护的是这一次请求,而不是全局开关
假设预览面板允许用户取消,也会随页面退出而失效,还要求等待时间有限。要避免的是:用户已经放弃,旧请求仍把结果写回界面;或者一次请求取消后,后面的请求复用了已取消信号而立即失败。本文用假设场景解释风险,没有声称发生过真实事故。
先分清拥有者:用户控制器属于本次操作,页面控制器属于页面生命周期,超时控制器与计时器属于本次请求。调用方负责何时触发取消,业务函数只接收 signal,不应擅自取消页面内其他操作。过宽的共享范围可能让一个局部动作结束整页工作,过窄又会漏掉页面退出这一来源。
组合信号只表达“任意一个来源取消”
userSignal、pageSignal 和 deadline.signal 是输入来源;AbortSignal.any 产生组合 signal;fetch 接收这个 signal;reason 保存使组合信号取消的原因。这些对象的职责是静态关联,不代表某种固定的发生顺序。

如果构造时已经有输入取消,返回信号立即处于已取消状态;存在多个已取消输入时,按输入迭代顺序采用其中第一个已取消信号的原因。输入都未取消时,则由随后最先使组合信号取消的来源提供 reason。后来的取消不会把组合信号重新激活。
最小实现要包含原因识别和清理
下面为每次读取创建一个自有超时来源,方便请求完成后 clearTimeout。八秒只是示例参数,不是服务等级或通用安全值。/api/preview 是演示用同源接口名称,不代表站点存在这个接口。调用方收到 kind 为 ok 后,仍需确认当前界面是否继续接受该次请求结果。
async function readPreview(userSignal, pageSignal) {
// 明确要求原生支持,不悄悄忽略某个取消来源。
if (typeof AbortSignal.any !== "function") {
throw new Error("当前环境不支持联合取消");
}
const deadline = new AbortController();
const signal = AbortSignal.any([
userSignal, pageSignal, deadline.signal,
]);
const timer = setTimeout(() => {
deadline.abort(new DOMException("预览等待超时", "TimeoutError"));
}, 8000);
try {
// 已取消输入会得到已取消组合信号,避免继续发起业务读取。
signal.throwIfAborted();
const response = await fetch("/api/preview", { signal });
if (!response.ok) {
throw new Error("预览请求返回 HTTP " + response.status);
}
const data = await response.json();
// 防止后续异步阶段已结束却继续接收取消后的结果。
signal.throwIfAborted();
return { kind: "ok", data };
} catch (error) {
// 只识别这次请求的取消原因,不把所有异常都吞作“用户取消”。
if (signal.aborted && Object.is(error, signal.reason)) {
return { kind: "cancelled", reason: signal.reason };
}
throw error;
} finally {
// 自己创建的计时器由自己清理,成功、失败或取消都覆盖。
clearTimeout(timer);
}
}
// 每次请求新建用户控制器;页面控制器由页面生命周期的拥有者管理。
const userController = new AbortController();
const pageController = new AbortController();
// 只展示调用入口,按需调用,示例没有自动发出网络请求。
// readPreview(userController.signal, pageController.signal);
// 用户取消时调用 userController.abort(new DOMException("用户取消", "AbortError"));
// 页面退出时由页面拥有者调用 pageController.abort(new DOMException("页面退出", "AbortError"));
示例把 AbortSignal.any 作为能力门槛,throwIfAborted 也属于所需 API。面对旧运行环境,应在部署前明确目标支持范围,或使用单独维护且经过适用环境测试的兼容方案;不能仅删除 any 后保留一个来源而不告知调用方取消语义已经改变。
取消原因可以是任意 JavaScript 值,不能假设总有 name 字段;示例选择 DOMException 只是便于调用方展示。在捕获处比较这次 signal.reason,可以减少把网络异常或 HTTP 错误误报成用户取消。外层仍应处理抛出的非取消错误,不能因为函数使用了取消信号就完全忽略异常。
取消不是回滚,也不替你管理所有来源
组合信号取消不会替其他输入调用 abort,也不会自动清理它们背后的定时器。示例 finally 清掉自有 timer,就是为了不让一次成功请求留下无需再触发的计时器。若改用 AbortSignal.timeout,则不能把它当成可 clearTimeout 的句柄;它的时间语义还涉及活动时间,不能直接宣传成精确的服务器截止时间。
fetch 接收取消信号,并不意味着所有自定义 Promise 都会遵守它;新增的异步任务必须主动支持信号或在关键位置检查状态。请求到达服务端后,客户端取消也不证明服务端没有执行操作。涉及写入仍要另行处理业务幂等和结果确认,不能用联合取消代替事务。
取消状态中的 aborted 与 reason 归属于组合信号;自有 timer 则由 finally 清理;源控制器保持自己的生命周期。这三个边界应分别考虑。手动注册的 abort 监听器,也要在操作结束时移除,once 只保证事件发生时调用一次,不等于成功完成时自动解除所有关联。

把边界检查放到具体场景里
// 无网络的小例子:两个来源不会互相取消。
const left = new AbortController();
const right = new AbortController();
const firstReason = new DOMException("不再需要结果", "AbortError");
const joined = AbortSignal.any([left.signal, right.signal]);
left.abort(firstReason);
// 按规范推导应依次为 true、true、false;本文未执行。
console.log(joined.aborted);
console.log(joined.reason === firstReason);
console.log(right.signal.aborted);
| 场景 | 应关注的结果 |
|---|---|
| 调用前输入已取消 | 组合信号已取消,业务不继续读取 |
| 用户取消 | 保留用户来源的 reason,不误标为超时 |
| 超时来源取消 | 组合取消并清理自有 timer |
| 请求正常完成 | finally 仍清理 timer,不复用这次请求状态 |
| 网络或 HTTP 失败 | 保留非取消错误,交由上层处理 |
这些是读者可实施的检查项,不是本文测试报告。记录原因时只保留必要类别,避免把对象内容、令牌或完整请求数据直接写到日志。它能帮助定位“谁先取消”,却不是完整审计系统。
能不能等所有来源都取消才停止?
any 表达任意来源取消,并不实现“全部取消”。如果业务要求全部满足才终止,需要另行定义状态组合,不应给 any 加一个误导性函数名。
组合 signal 能调用 abort 吗?
不能把信号当作控制器。触发取消应通过拥有的 AbortController;组合信号负责传递取消状态。
下一次请求能继续复用已经取消的 signal 吗?
不适合。取消不可重置。下一次操作新建请求级控制器;若页面级信号已取消,也应重新评估页面生命周期,而不是继续把它放入新组合。
联合取消的核心是明确来源拥有者、给消费 API 传入组合信号,并处理原因与资源收尾。它减少重复监听代码,但不会自动完成权限、服务端回滚或所有异步工作的生命周期管理。
-
502 收藏
-
501 收藏
-
501 收藏
-
501 收藏
-
501 收藏
-
478 收藏
-
417 收藏
-
162 收藏
-
262 收藏
-
458 收藏
-
文章 · 前端 | 1小时前 | 前端 · 性能监控 · javascript · 浏览器API · 静态资源 前端性能 PerformanceObserver PerformanceResourceTiming 资源等待时间215 收藏
-
389 收藏
-
479 收藏
-
329 收藏
-
243 收藏
-
301 收藏
-
493 收藏
-
- 前端进阶之JavaScript设计模式
- 设计模式是开发人员在软件开发过程中面临一般问题时的解决方案,代表了最佳的实践。本课程的主打内容包括JS常见设计模式以及具体应用场景,打造一站式知识长龙服务,适合有JS基础的同学学习。
- 立即学习 543次学习
-
- GO语言核心编程课程
- 本课程采用真实案例,全面具体可落地,从理论到实践,一步一步将GO核心编程技术、编程思想、底层实现融会贯通,使学习者贴近时代脉搏,做IT互联网时代的弄潮儿。
- 立即学习 516次学习
-
- 简单聊聊mysql8与网络通信
- 如有问题加微信:Le-studyg;在课程中,我们将首先介绍MySQL8的新特性,包括性能优化、安全增强、新数据类型等,帮助学生快速熟悉MySQL8的最新功能。接着,我们将深入解析MySQL的网络通信机制,包括协议、连接管理、数据传输等,让
- 立即学习 500次学习
-
- JavaScript正则表达式基础与实战
- 在任何一门编程语言中,正则表达式,都是一项重要的知识,它提供了高效的字符串匹配与捕获机制,可以极大的简化程序设计。
- 立即学习 487次学习
-
- 从零制作响应式网站—Grid布局
- 本系列教程将展示从零制作一个假想的网络科技公司官网,分为导航,轮播,关于我们,成功案例,服务流程,团队介绍,数据部分,公司动态,底部信息等内容区块。网站整体采用CSSGrid布局,支持响应式,有流畅过渡和展现动画。
- 立即学习 485次学习