当前位置:首页 > 文章列表 > 文章 > 前端 > 前端怎样合并多个取消信号

前端怎样合并多个取消信号

来源:17golang原创 2026-10-18 02:34:45 0浏览 收藏

前端合并多个取消来源,可以把各个 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 保存使组合信号取消的原因。这些对象的职责是静态关联,不代表某种固定的发生顺序。

三个取消来源与组合信号和请求的静态关系
图1:结构说明图。取消来源与请求消费分组分开阅读,任意输入都可影响组合信号,但不会反向取消其他输入。

如果构造时已经有输入取消,返回信号立即处于已取消状态;存在多个已取消输入时,按输入迭代顺序采用其中第一个已取消信号的原因。输入都未取消时,则由随后最先使组合信号取消的来源提供 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 只保证事件发生时调用一次,不等于成功完成时自动解除所有关联。

取消状态、原因与自有计时器清理职责
图2:结构说明图。取消状态和资源清理分别分组,强调 reason 是状态信息,而 finally 负责自有 timer;不是运行证据。

把边界检查放到具体场景里

// 无网络的小例子:两个来源不会互相取消。
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 传入组合信号,并处理原因与资源收尾。它减少重复监听代码,但不会自动完成权限、服务端回滚或所有异步工作的生命周期管理。

版本声明
本文转载于:17golang原创 如有侵犯,请联系study_golang@163.com删除
Linux 怎样读取 cgroup v2 内存压力事件Linux 怎样读取 cgroup v2 内存压力事件
上一篇
Linux 怎样读取 cgroup v2 内存压力事件
前端 Request 读取一次后怎样再次发送
下一篇
前端 Request 读取一次后怎样再次发送
查看更多
最新文章
查看更多
课程推荐
  • 前端进阶之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推荐
  • PubMedQA数据集详解:生物医学问答基准、功能与应用指南
    PubMedQA
    深入了解PubMedQA生物医学问答数据集,涵盖其核心功能、使用方法及在临床决策、药物研发等场景的应用,助力提升NLP模型性能。
    511次使用
  • H2O EvalGPT:开源LLM大模型评估与排行榜工具
    H2O EvalGPT
    H2O EvalGPT是H2O.ai推出的开源LLM评估平台,提供详细的大模型性能排行榜、行业特定基准测试及A/B测试功能,助您快速选择最适合项目的高性能大语言模型。
    571次使用
  • LMArena是什么?伯克利AI模型评估平台使用指南与功能解析
    LMArena
    LMArena是加州大学伯克利分校推出的AI模型匿名评测平台。通过盲测投票机制,用户可对比不同大模型回答并生成实时排行榜,助力开发者优化模型及用户选择最佳AI工具。
    597次使用
  • 斯坦福HELM:大语言模型Holistic Evaluation整体评估框架详解
    HELM
    深入了解斯坦福推出的HELM(Holistic Evaluation of Language Models)大模型评测体系。本文解析其核心功能、安装配置步骤及应用场景,涵盖准确性、公平性、鲁棒性等多维度指标,助力开发者全面优化语言模型性能。
    549次使用
  • MMBench详解:多模态大模型基准测试、功能特点与使用指南
    MMBench
    MMBench是由上海人工智能实验室等机构联合推出的多模态基准测试平台,提供细粒度能力评估、大规模数据集及VLMEvalKit工具。本文详细介绍其核心功能、安装使用方法及应用场景,助力开发者全面评估多模态模型性能。
    365次使用