前端进阶之旅前端进阶之旅
基础篇
进阶篇
高频篇
精选篇
手写篇
面经篇
AI 篇
原理篇
每日一题
小程序题库
知识卡片
  • 场景篇按分类整理的大前端场景考点
  • 历年面经按年份追踪真实考点
  • 算法题库NEW在线编码即时判题
  • 专项自测100 题快速查漏
  • 前端基础
    • HTTP从报文一路讲到 HTTPS
    • 浏览器渲染、事件循环、进程
    • 计算机基础Linux、网络、操作系统
  • 进阶专项
    • 设计模式23 种模式怎么用
    • 前端系统进阶学习大型项目工程化
    • 前端综合文章长期沉淀的实践文
  • 工程与工具
    • Node学习指南从环境搭建到服务端
    • NPM工作流script、依赖与发布
    • Docker容器化部署上手
    • Canvas图形与动画实战
  • 路线与导图
    • 思维导图知识点全景图
    • 学习路线按图索骥不跑偏
  • 动态
    • 公众号动态公众号历史文章
    • 博客动态站长的技术博客
    • 开发者导航常用工具与文档站
  • AI 助手随时提问,即时解析
  • AI 模拟面试模拟真实面试 + 报告
  • AI 知识地图串起全站知识点
  • AI 定制路线按你的简历现排
AI 热点
旧版
基础篇
进阶篇
高频篇
精选篇
手写篇
面经篇
AI 篇
原理篇
每日一题
小程序题库
知识卡片
  • 场景篇按分类整理的大前端场景考点
  • 历年面经按年份追踪真实考点
  • 算法题库NEW在线编码即时判题
  • 专项自测100 题快速查漏
  • 前端基础
    • HTTP从报文一路讲到 HTTPS
    • 浏览器渲染、事件循环、进程
    • 计算机基础Linux、网络、操作系统
  • 进阶专项
    • 设计模式23 种模式怎么用
    • 前端系统进阶学习大型项目工程化
    • 前端综合文章长期沉淀的实践文
  • 工程与工具
    • Node学习指南从环境搭建到服务端
    • NPM工作流script、依赖与发布
    • Docker容器化部署上手
    • Canvas图形与动画实战
  • 路线与导图
    • 思维导图知识点全景图
    • 学习路线按图索骥不跑偏
  • 动态
    • 公众号动态公众号历史文章
    • 博客动态站长的技术博客
    • 开发者导航常用工具与文档站
  • AI 助手随时提问,即时解析
  • AI 模拟面试模拟真实面试 + 报告
  • AI 知识地图串起全站知识点
  • AI 定制路线按你的简历现排
AI 热点
旧版
首页程序员面试题库AbortSignal.any
JavaScript异步与 Promise

一个请求既要能被用户取消又要有超时,AbortSignal.any 怎么用?

AbortSignal.any([a, b]) 把多个 signal 合成一个新的 signal,数组里任意一个被取消,新 signal 就立刻跟着取消。把它传给 fetch,用户点取消、超时、组件卸载,哪个先发生都能中断请求。

前端进阶之旅 · 一题精讲更新于 2026.09.29
JavaScript#异步与 Promise#异步编程
先看核心答案读代码示例
理解线索

多个取消源合成一个

  1. 来源 signal各自独立,比如按钮上的 controller.signal 和 AbortSignal.timeout(8000)
  2. AbortSignal.any()监听所有来源,第一个取消时把自己也取消
  3. signal.reason等于最先取消的那个来源的 reason,用来区分原因

AbortSignal.any 只能读,不能反过来取消来源。想在代码里主动取消,还是要调用某个来源 controller 的 abort()。

核心回答

先记住这个答案

AbortSignal.any() 接收一个 signal 数组,返回一个新的 signal。只要数组里任意一个 signal 被取消,新 signal 就跟着取消,它的 reason 就是最先取消的那个 signal 的 reason。典型用法是把用户手动取消的 controller.signal 和 AbortSignal.timeout(ms) 合在一起传给 fetch,这样一个请求同时拥有两个取消源,谁先触发都能中断它。

  • AbortSignal.any 是「任意一个取消就取消」,不是全部
  • 合成 signal 的 reason 取自最先取消的那个
  • 数组里有已取消的 signal,结果一创建就是已取消
  • 超时报 TimeoutError,手动取消报 AbortError

用户取消加超时,合成一个 signal

比如一个导出报表的按钮,用户可以点「取消」,同时请求最多等 8 秒。以前要自己写 setTimeout 去调 abort(),现在直接合成。

两个取消源,一个请求JavaScript
const userController = new AbortController() // 绑在「取消」按钮上
cancelButton.onclick = () => userController.abort()

async function exportReport() {
  const signal = AbortSignal.any([
    userController.signal, // 用户点取消
    AbortSignal.timeout(8000) // 8 秒超时
  ])
  try {
    const res = await fetch('/api/report/export', { signal })
    return await res.blob()
  } catch (err) {
    if (err.name === 'TimeoutError') showToast('导出超时,请稍后重试')
    else if (err.name === 'AbortError') showToast('已取消导出')
    else throw err
  }
}

fetch 被取消时,拒绝的错误就是 signal 的 reason。AbortSignal.timeout 超时后 reason 是名为 TimeoutError 的错误,controller.abort() 不传参数时是 AbortError,所以靠 err.name 就能分清是谁取消的。 如果调用 abort() 时传了自己的原因,比如 abort(new Error('页面切走')),那 fetch 拒绝的就是这个 Error,判断时要按你自己定的规则来。

合成 signal 的几个行为细节

AbortSignal.any 的规则很少,但每一条面试都可能追问。

已经取消的 signal 传进去会怎样
数组里只要有一个在传入时已经是取消状态,返回的 signal 一创建就是 aborted: true,它的 reason 就是那个 signal 的 reason。把它传给 fetch,请求根本不会发出,Promise 立刻拒绝。
取消只往一个方向传
来源取消会传给合成 signal,反过来不行。合成 signal 本身没有 abort() 方法,它不是 controller。想「一键取消所有」,就要额外建一个 controller 作为来源之一,调用它的 abort()。
空数组
AbortSignal.any([]) 返回一个永远不会被取消的 signal,因为没有任何来源能触发它。

老环境下怎么手写一个

AbortSignal.any 是比较新的 API,要兼容老浏览器时可以自己实现。思路是新建一个 controller,给每个来源注册一次 abort 事件监听,谁先触发就把它的 reason 转给自己。

手写 anySignalJavaScript
function anySignal(signals) {
  const controller = new AbortController()
  for (const s of signals) {
    if (s.aborted) { controller.abort(s.reason); break } // 已取消的直接生效
    s.addEventListener('abort', () => controller.abort(s.reason), {
      once: true,
      signal: controller.signal // 合成 signal 取消后,自动移除其余监听
    })
  }
  return controller.signal
}

最后那个 signal: controller.signal 很关键。长期存在的来源,比如整个页面生命周期里的一个 controller,如果每次请求都往它身上挂监听却不移除,监听会越积越多。用 addEventListener 的 signal 选项,合成 signal 一旦取消,其余来源上的监听会被一并移除。

回答前,多想一步

容易答错的地方

把 any 理解成全部取消才取消
AbortSignal.any 的语义和 Promise.any 不一样。Promise.any 是等任意一个成功,AbortSignal.any 是任意一个取消就取消,更接近 Promise.race 的「谁先来算谁」。
以为取消了合成 signal 能停掉来源
合成 signal 是只读的。比如超时触发后,用户的 userController 并不会变成已取消,下次请求它还能继续用。
每次请求复用同一个合成 signal
合成 signal 一旦取消就永远是取消状态。超时来源 AbortSignal.timeout 也是从创建时开始计时的,所以要在每次发请求时现场调用 AbortSignal.any,而不是在模块顶部建一个全局共用。
试着用自己的话回答

面试官还会怎么问?

怎么知道到底是哪个来源触发了取消?

看合成 signal 的 reason,它就是最先取消的来源的 reason。也可以逐个检查来源的 aborted 属性,但多个来源都可能已经取消,reason 更准确。给不同来源传不同的 reason,判断起来最清楚。

React 组件里怎么用?

在 useEffect 里建一个 controller,清理函数里调用 abort(),代表组件卸载。发请求时把它和 AbortSignal.timeout(ms) 合成一个,就同时有了卸载取消和超时取消。

一个封装好的请求函数要不要接收外部的 signal?

要。函数内部可以有自己的超时,同时接收调用方传进来的 signal,两者用 AbortSignal.any 合成。这样调用方想取消时不用了解内部细节,内部的超时也不会被外部覆盖掉。

从一道题,走向一组知识

把知识连起来

异步与 Promise

Promise.all 里一个请求挂了,怎么让其他请求的结果还能用?

用一个合成的 signal 同时控制多个并发请求时,被取消的请求也会让 Promise.all 拒绝,需要那篇的办法保住其他结果。

异步与 Promise

Promise.allSettled 返回的数组里,每一项长什么样?

批量请求里有的被取消、有的成功,用 Promise.allSettled 能拿到每一个的状态和取消原因。

参考资料

  • JavaScript:异步与 Promise参考资料

示例用于理解所注明的运行环境与边界;延伸学习可结合原文中的更多案例。

本题目录
  1. 先记住这个答案
  2. 用户取消加超时,合成一个 signal
  3. 合成 signal 的几个行为细节
  4. 老环境下怎么手写一个
  5. 容易答错的地方
  6. 面试官还会怎么问
  7. 把知识连起来
读懂,再试着讲出来

先看核心答案,再读代码。最后展开追问,检查自己有没有遗漏边界。

试着回答追问
浏览全部面试题理解原理,也关注真实的使用场景。回到顶部 ↑