当前位置:首页 > 文章列表 > 文章 > 前端 > Fetch AbortController 如何取消超时请求

Fetch AbortController 如何取消超时请求

来源:17golang原创 2026-09-12 16:48:56 0浏览 收藏

给前端 fetch 加超时,不是给 Promise 外面套一个 setTimeout 就结束了。真正要中止网络请求,需要把 AbortSignal 传进 fetchsignal 选项;超时后由信号触发取消。现代浏览器可以直接使用 AbortSignal.timeout(ms),并在捕获异常时把 TimeoutError 和用户主动取消产生的 AbortError 分开处理。

要点速览
  • 每次请求创建新的 AbortController 或超时信号,已终止的信号不能复用。
  • AbortSignal.timeout(5000) 会在超时后终止 fetch,但不会把 HTTP 500 自动变成异常。
  • 捕获异常时优先判断 TimeoutErrorAbortError,最后再处理网络错误或其他异常。
  • 需要兼容较旧浏览器或明确清理定时器时,可用 AbortController + setTimeout 回退。

1. 为每次请求创建独立的 AbortSignal

AbortController 负责发出取消动作,controller.signal 是传给异步 API 的只读信号。请求开始前创建它,调用 controller.abort() 后,使用这个信号的 fetch 会被拒绝。

控制器不要放成一个永久复用的全局变量。一个信号一旦进入 aborted 状态,后续再把它交给新的请求,新请求会立即失败。独立创建也能避免“搜索框上一次请求取消了下一次请求”的串扰。

async function requestJson(url, controller) {
  // 每个请求使用自己的 signal,避免复用已经 aborted 的信号。
  const response = await fetch(url, { signal: controller.signal });

  // fetch 遇到 404/500 仍可能正常返回 Response,这里显式检查状态。
  if (!response.ok) {
    throw new Error(`HTTP ${response.status}`);
  }

  // 读取 body 也属于异步阶段,读取前后都可能受到取消影响。
  return response.json();
}

const controller = new AbortController();
requestJson('/api/profile', controller)
  .catch((error) => console.error(error));

// 需要取消时调用;生产代码通常由按钮、路由切换或组件卸载触发。
// controller.abort();
前端 Fetch 请求中 AbortController、AbortSignal 与响应体读取的关系示意图
图1:操作示意图,展示独立 AbortController 如何把取消信号传入 Fetch 请求。

2. 用 AbortSignal.timeout 绑定请求超时

只关心“超过 5 秒就停止”的场景,可以把超时信号直接传给 fetch。它比在外层竞争两个 Promise 更可靠,因为实际的 Fetch 操作确实收到了终止信号。

async function fetchWithTimeout(url, timeoutMs = 5000) {
  // timeout 到期后 signal 会自动 aborted,时间单位是毫秒。
  const signal = AbortSignal.timeout(timeoutMs);
  const response = await fetch(url, { signal });

  // 网络请求成功不等于业务状态成功,继续检查 HTTP 状态码。
  if (!response.ok) {
    throw new Error(`HTTP ${response.status}`);
  }

  // body 读取也受同一个 signal 影响,不能只保护 fetch 调用本身。
  return response.json();
}

这里有一个容易误判的边界:fetch 只会在请求失败或被取消时拒绝,服务器返回 404、500 等 HTTP 状态并不会自动进入 catch。因此超时取消与 HTTP 错误必须分别处理。

3. 按异常名称区分超时、主动取消和网络失败

AbortSignal.timeout() 超时通常以名称为 TimeoutErrorDOMException 拒绝;调用控制器的 abort() 则通常对应 AbortError。不要只写一个“请求失败”的提示,否则用户主动离开页面也会被记录成网络故障。

async function loadUser(url) {
  try {
    const response = await fetch(url, {
      // 让浏览器在 8 秒后终止仍未完成的请求。
      signal: AbortSignal.timeout(8000),
    });

    if (!response.ok) {
      throw new Error(`HTTP ${response.status}`);
    }
    return await response.json();
  } catch (error) {
    // TimeoutError 表示自动超时,可以提示用户稍后重试。
    if (error.name === 'TimeoutError') {
      throw new Error('请求超过 8 秒仍未完成');
    }

    // AbortError 更接近用户取消、页面离开等主动终止。
    if (error.name === 'AbortError') {
      return null;
    }

    // 其他错误可能是网络断开、CORS 或代码问题,保留原始原因用于排查。
    throw error;
  }
}

如果还要判断信号状态,可以检查 signal.abortedsignal.reason。但不要用“进入了 catch”作为超时判断,因为 DNS、CORS、断网和解析失败同样可能进入 catch。

Fetch 请求按 TimeoutError、AbortError、HTTP 状态和网络错误分流的结果示意图
图2:结果示意图,展示超时、主动取消、HTTP 错误和网络错误应分别进入不同处理分支。

4. 合并用户取消与自动超时

搜索建议、文件下载和路由切换常常同时需要两种取消来源:用户点击取消,或者请求超过预算自动停止。可以用 AbortSignal.any() 合并它们,任意一个信号终止都会让 Fetch 停止。

async function fetchCancelable(url, cancelButton, timeoutMs = 5000) {
  const userController = new AbortController();
  const timeoutSignal = AbortSignal.timeout(timeoutMs);

  // 任一来源终止,combinedSignal 都会终止当前 fetch。
  const combinedSignal = AbortSignal.any([
    userController.signal,
    timeoutSignal,
  ]);

  const onCancel = () => userController.abort();
  cancelButton.addEventListener('click', onCancel, { once: true });

  try {
    const response = await fetch(url, { signal: combinedSignal });
    if (!response.ok) throw new Error(`HTTP ${response.status}`);
    return await response.json();
  } finally {
    // 请求结束后移除自定义监听,避免长期持有按钮和闭包引用。
    cancelButton.removeEventListener('click', onCancel);
  }
}

any() 的便利之处是统一入口,代价是最终信号只表达“已经取消”,不能可靠地告诉你是哪个输入先触发。若产品必须精确记录“用户取消”和“超时”两个指标,可以保留自己的 timedOut 标记,或使用手动控制器加定时器的方案。

5. 兼容回退与发布前检查清单

较旧环境不一定支持 AbortSignal.timeout()。这时使用普通 AbortController,在 finally 中清理定时器即可;这种写法还可以明确取消超时计时器。

async function fetchWithFallback(url, timeoutMs = 5000) {
  const controller = new AbortController();
  let timedOut = false;

  // 定时器只负责触发 abort,真正的请求终止仍由 signal 传递。
  const timer = setTimeout(() => {
    timedOut = true;
    controller.abort();
  }, timeoutMs);

  try {
    const response = await fetch(url, { signal: controller.signal });
    if (!response.ok) throw new Error(`HTTP ${response.status}`);
    return await response.json();
  } catch (error) {
    // 回退方案需要自己标记超时,因为异常名称通常只是 AbortError。
    if (timedOut) throw new Error('请求超时');
    if (error.name === 'AbortError') return null;
    throw error;
  } finally {
    // 成功、失败、取消都要清除计时器,避免无意义的回调继续执行。
    clearTimeout(timer);
  }
}
现象优先判断处理建议
超过预算后停止TimeoutError 或自定义 timedOut提示重试,记录耗时
用户点取消或路由离开AbortError通常静默结束,不当成系统故障
返回 4xx/5xxresponse.ok按业务状态展示服务端错误
断网、CORS、解析异常其他异常保留原始错误并进入通用故障处理

上线前至少检查五件事:信号是否每次请求独立创建;signal 是否真的传入 fetch;响应状态是否单独判断;响应体读取是否仍在取消保护范围内;自定义事件和定时器是否在 finally 中清理。这样处理后,Fetch 的超时、取消和网络失败就不会再被一条模糊的“请求失败”吞掉。

常见问题

AbortSignal.timeout 的参数单位是什么?

单位是毫秒,例如 AbortSignal.timeout(5000) 表示约 5 秒的活动时间预算。

fetch 返回 500 会触发 catch 吗?

不会仅因为状态码触发。先检查 response.okresponse.status,再按业务抛出异常。

一个 AbortController 能给多个请求使用吗?

可以让多个请求共享同一个取消动作,但控制器一旦 abort,所有关联请求都会被终止;已终止的 signal 不适合启动新请求。

AbortSignal.any 能告诉我是谁触发取消吗?

它适合统一取消入口,但不能可靠区分最终来源。需要统计来源时,额外维护用户取消标记和超时标记。

版本声明
本文转载于:17golang原创 如有侵犯,请联系study_golang@163.com删除
Linux inotify 监听数量不足时如何确认限制Linux inotify 监听数量不足时如何确认限制
上一篇
Linux inotify 监听数量不足时如何确认限制
Go maps.Clone 如何复制映射并处理 nil 输入
下一篇
Go maps.Clone 如何复制映射并处理 nil 输入
查看更多
最新文章
查看更多
课程推荐
  • 前端进阶之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测试功能,助您快速选择最适合项目的高性能大语言模型。
    104次使用
  • LMArena是什么?伯克利AI模型评估平台使用指南与功能解析
    LMArena
    LMArena是加州大学伯克利分校推出的AI模型匿名评测平台。通过盲测投票机制,用户可对比不同大模型回答并生成实时排行榜,助力开发者优化模型及用户选择最佳AI工具。
    18次使用
  • OpenCompass大模型评测体系详解:功能、使用指南与应用场景
    OpenCompass
    OpenCompass是上海AI实验室推出的开源大模型评测平台,提供CompassKit、CompassHub和CompassRank三大核心组件,支持LLM及多模态模型的一站式标准化评估与排行榜查询。
    31次使用
  • AGI-Eval大模型评测平台:权威榜单、数据集与人机协同评测方案
    AGI-Eval
    AGI-Eval是由上海交大等高校联合发布的大模型评测社区,提供公正透明的LLM能力榜单、多领域评测集及Data Studio数据服务,助力AI模型性能评估与NLP科研开发。
    20次使用
  • SuperCLUE中文大模型评测基准:功能、能力维度与应用指南
    SuperCLUE
    SuperCLUE是权威的中文大语言模型综合评测基准,涵盖语言理解、知识应用、AI Agent智能体及安全性等12项核心能力。通过多轮对话与客观测试,定期发布榜单与技术报告,为模型研发、优化及行业选型提供科学依据。
    257次使用
微信登录更方便
  • 密码登录
  • 注册账号
登录即同意 用户协议隐私政策
返回登录
  • 重置密码