JavaScript fetch 怎样设置超时并可取消
JavaScript fetch 设置超时并允许手动取消,可以把 AbortSignal.timeout 产生的超时信号与 AbortController 的用户信号通过 AbortSignal.any 合并,再传给 fetch 的 signal。封装 JSON 请求时,应把 response.json() 也放进这次操作范围;只等到响应头就结束计时,会漏掉缓慢的响应体。
这里把它看成“调用方拥有取消权”的请求边界:上层决定何时放弃,下层负责把信号传到网络读取,并处理自己的资源。示例针对浏览器,接口地址为占位路径,本文没有运行代码或构造实际网络结果。
把请求取消作为调用契约
搜索框切换查询、页面离开、用户点取消,都可能让请求失去价值。只设置一个全局超时不够:不同调用有不同的预算,也不应互相取消。一个独立操作配一个控制器,下层接收信号而不是直接控制整个页面。
AbortSignal 是单次信号,中止后不能恢复。下一次请求应创建新的控制器。复用已取消信号会让新请求立即失败;有意共享同一信号则代表多个请求属于同一个取消域。接口语义依据 WHATWG DOM 标准。
用组合信号实现超时与手动取消
async function fetchJson(url, { signal, timeoutMs = 6000 } = {}) {
// 约束参数范围,避免无意间把秒当成毫秒
if (!Number.isInteger(timeoutMs) || timeoutMs 2147483647) {
throw new RangeError("timeoutMs 必须是有效的非负毫秒整数");
}
const deadline = AbortSignal.timeout(timeoutMs);
// 外部取消与超时共用一个信号,最先中止的原因保留下来
const combined = signal ? AbortSignal.any([signal, deadline]) : deadline;
try {
const response = await fetch(url, { signal: combined });
if (!response.ok) {
// 不读取错误响应体,主动取消未消费的流;清理失败不覆盖 HTTP 错误
if (response.body) await response.body.cancel().catch(() => {});
throw new Error(`HTTP ${response.status}`);
}
// await 放在函数内,整个读取操作都受同一个取消信号约束
return await response.json();
} catch (error) {
// 仅当拒绝值就是该信号原因时,按取消归类,避免掩盖独立错误
if (combined.aborted && error === combined.reason) throw combined.reason;
throw error;
}
}这里的 timeoutMs 使用毫秒,6000 只是示例预算,不是所有接口的推荐值。运行前应确认目标环境提供 timeout 和 any 两个静态方法,不能只检测 AbortController 就假设全部支持。传入的 signal 应是有效 AbortSignal;不接受伪造的普通对象。
any 的组合结果在任一来源中止时中止,并携带触发来源的原因。输入已经中止时,组合结果也立即中止;多个输入事先都已中止时,按输入顺序采用第一个。没有传外部信号时,无需额外合并。

// 每次操作新建控制器,把同一信号交给这一操作的请求
const userCancel = new AbortController();
const task = fetchJson("/api/catalog", {
signal: userCancel.signal,
timeoutMs: 6000,
});
// 立即接住异步失败;实际页面可改为展示相应提示
task.catch((error) => {
if (error?.name === "TimeoutError") console.info("请求超时");
else if (error?.name === "AbortError") console.info("已取消");
else console.error("请求失败", error);
});
// 将下面这一行放进真实取消事件回调;不需要取消时不要执行
// userCancel.abort(new DOMException("用户取消查询", "AbortError"));示例约定用户取消使用 AbortError、超时使用 TimeoutError。调用方如果自定义 abort 的 reason,它可以是任意 JavaScript 值,就不能继续假定每个拒绝值都有 name 属性。业务封装应明确自己的错误契约,而不是把所有异常都提示成网络中断。
让超时覆盖响应体读取
fetch 的 Promise 可以在响应体尚未读完时兑现。随后 json 或 text 还要消费流,期间取消仍可能导致读取失败。示例把 await response.json() 留在封装内,因此调用方拿到的是解析后的数据,不是一个尚待消费的 Response。相关网络与流取消语义见 WHATWG Fetch 标准。
HTTP 404、500 不会仅因为状态码就使 fetch 拒绝,因此代码显式检查 response.ok。JSON 格式错误、网络异常和中止也不是一类问题。错误发生后不能只看某个来源信号后来是否中止就改写原异常;示例在需要核对时把拒绝值与组合信号 reason 对齐。
这个边界是网络读取预算,不是抢占式 CPU 限时器。JavaScript 同步解析或业务计算正在占用线程时,取消信号不能强制打断它。原生 timeout 基于活跃时间,页面或 worker 暂停可能影响到期时刻,不能把它当成严格的墙钟服务端截止期限。
旧环境用定时器和监听器桥接
如果环境有基本取消能力但缺少两个静态方法,可以用本地控制器连接外部信号和定时器。这个兼容版本按同一参数约定工作,重点是成功和失败都清理资源,而不是仅在超时后清理。
async function fetchJsonCompat(url, { signal, timeoutMs = 6000 } = {}) {
// 仅替代 timeout/any;仍要求支持 fetch、AbortController 和 signal.reason
if (!Number.isInteger(timeoutMs) || timeoutMs 2147483647) {
throw new RangeError("timeoutMs 超出定时器安全范围");
}
const local = new AbortController();
const forward = () => local.abort(signal.reason);
let timer;
try {
// 已取消的输入不能等待事件再次触发
if (signal) {
if (signal.aborted) forward();
else signal.addEventListener("abort", forward, { once: true });
}
if (!local.signal.aborted) {
timer = setTimeout(() => {
local.abort(new DOMException("请求超时", "TimeoutError"));
}, timeoutMs);
}
const response = await fetch(url, { signal: local.signal });
if (!response.ok) {
if (response.body) await response.body.cancel().catch(() => {});
throw new Error(`HTTP ${response.status}`);
}
return await response.json();
} finally {
// 成功、HTTP 错误、网络错误及取消都走同一清理出口
clearTimeout(timer);
signal?.removeEventListener("abort", forward);
}
}

finally 包围响应体读取,意味着无论读取成功还是中途拒绝,定时器与外部监听都会释放。只有 once 并不够:成功请求从未触发 abort,监听器就不会自动因 once 消失。另一方面,原生 timeout 没有与 clearTimeout 对应的公开取消方法,any 也不会替你中止其他来源。
这不是 timeout 的完全语义 polyfill:普通定时器受到页面调度影响,旧引擎还可能缺少自定义中止原因支持。若连基础 AbortController 都没有,应采用经过项目兼容评估的实现或调整支持范围,不能用一个名字相同的空函数伪装已经取消网络。
不要把取消当成回滚
Promise.race 只能让调用方先收到某个结果;如果没有给 fetch 传入真正的取消信号,败下来的请求仍可能继续。即便正确调用 abort,服务端也可能已经收到请求并完成写入,客户端取消不提供事务回滚承诺。
查询取消后通常可以忽略旧结果;支付、创建订单等写操作则需要服务端幂等设计、状态查询和明确业务协议,不能因客户端超时就自动认为失败并无条件重试。相邻请求还应通过请求标识避免旧响应覆盖新状态,取消不是所有界面竞态的替代品。
| 需求 | 对应机制 | 不保证什么 |
|---|---|---|
| 用户放弃请求 | 控制器与外部信号 | 服务端撤回已提交操作 |
| 限定网络等待 | 超时信号或可清理定时器 | 同步代码精确限时 |
| 组合多种取消 | any 或事件桥接 | 中止后能够恢复信号 |
接入时核对的边界和疑问
接入时明确:超时单位、浏览器支持范围、响应体由哪层消费、用户取消原因、HTTP 错误格式、组件离开时谁中止,以及兼容实现谁清理监听器。若封装返回 Response 而不是 JSON,响应体消费和清理责任必须一并转交,不能照搬本文函数的生命周期。
能给 fetch 直接加 timeout 参数吗?标准 RequestInit 没有一个通用 timeout 字段;应传 signal。第三方客户端的同名选项属于其自身 API,不能直接套到原生 fetch。
取消后下一次请求为什么立即报错?很可能复用了已经中止的信号。每次独立操作创建新控制器,只在需要成组取消时共享。
超时和手动取消都发生时怎样分类?以真正触发组合中止的 reason 为准,并遵守调用方约定的错误形状;不要在 catch 中重新比较当前所有来源的 aborted 状态推测先后。
把取消权、读取范围和清理责任写进请求函数的契约,比给每个调用临时套一个延时 Promise 更容易维护。本文代码与图示均是原创说明示例,具体效果仍由实际浏览器、接口及业务协议决定。
CSS :has() 按表单校验状态切换父级样式
- 上一篇
- CSS :has() 按表单校验状态切换父级样式
- 下一篇
- JavaScript ReadableStream 怎样逐块处理响应
-
- 文章 · 前端 | 25分钟前 | 性能优化 · ArrayBuffer TypedArray 前端性能 Web Worker Transferable
- Web Worker 怎样传输大数组减少复制
- 414浏览 收藏
-
- 文章 · 前端 | 27分钟前 | IntersectionObserver 前端性能 rootMargin threshold 滚动监听抖动
- IntersectionObserver 怎样避免滚动监听抖动
- 477浏览 收藏
-
- 文章 · 前端 | 40分钟前 | javascript · JavaScript Fetch ReadableStream 流式响应 TextDecoderStream
- JavaScript ReadableStream 怎样逐块处理响应
- 150浏览 收藏
-
- 文章 · 前端 | 44分钟前 |
- CSS :has() 按表单校验状态切换父级样式
- 214浏览 收藏
-
- 文章 · 前端 | 50分钟前 |
- Service Worker 怎样只缓存同源静态资源
- 189浏览 收藏
-
- 文章 · 前端 | 16小时前 |
- 前端 WebSocket 退避重连 怎样处理
- 278浏览 收藏
-
- 文章 · 前端 | 16小时前 |
- 前端 Vue watch 深度监听 怎样处理
- 192浏览 收藏
-
- 文章 · 前端 | 16小时前 | React 竞态 Fetch AbortController useEffect 前端异步
- 前端 React useEffect 竞态 怎样处理
- 107浏览 收藏
-
- 文章 · 前端 | 16小时前 |
- 前端 Vite alias 测试失效 怎样处理
- 483浏览 收藏
-
- 文章 · 前端 | 16小时前 |
- 前端 Vite 环境变量前缀 怎样处理
- 161浏览 收藏
-
- 文章 · 前端 | 16小时前 | 前端 · 性能 · javascript · ArrayBuffer postMessage 结构化克隆 Web Worker 前端计算
- 前端 Web Worker 移出计算 怎样处理
- 121浏览 收藏
-
- 文章 · 前端 | 16小时前 |
- 前端 DataView 读取二进制字段 怎样避免字节序错误
- 248浏览 收藏
-
- 前端进阶之JavaScript设计模式
- 设计模式是开发人员在软件开发过程中面临一般问题时的解决方案,代表了最佳的实践。本课程的主打内容包括JS常见设计模式以及具体应用场景,打造一站式知识长龙服务,适合有JS基础的同学学习。
- 543次学习
-
- GO语言核心编程课程
- 本课程采用真实案例,全面具体可落地,从理论到实践,一步一步将GO核心编程技术、编程思想、底层实现融会贯通,使学习者贴近时代脉搏,做IT互联网时代的弄潮儿。
- 516次学习
-
- 简单聊聊mysql8与网络通信
- 如有问题加微信:Le-studyg;在课程中,我们将首先介绍MySQL8的新特性,包括性能优化、安全增强、新数据类型等,帮助学生快速熟悉MySQL8的最新功能。接着,我们将深入解析MySQL的网络通信机制,包括协议、连接管理、数据传输等,让
- 500次学习
-
- JavaScript正则表达式基础与实战
- 在任何一门编程语言中,正则表达式,都是一项重要的知识,它提供了高效的字符串匹配与捕获机制,可以极大的简化程序设计。
- 487次学习
-
- 从零制作响应式网站—Grid布局
- 本系列教程将展示从零制作一个假想的网络科技公司官网,分为导航,轮播,关于我们,成功案例,服务流程,团队介绍,数据部分,公司动态,底部信息等内容区块。网站整体采用CSSGrid布局,支持响应式,有流畅过渡和展现动画。
- 485次学习
-
- PubMedQA
- 深入了解PubMedQA生物医学问答数据集,涵盖其核心功能、使用方法及在临床决策、药物研发等场景的应用,助力提升NLP模型性能。
- 615次使用
-
- H2O EvalGPT
- H2O EvalGPT是H2O.ai推出的开源LLM评估平台,提供详细的大模型性能排行榜、行业特定基准测试及A/B测试功能,助您快速选择最适合项目的高性能大语言模型。
- 654次使用
-
- LMArena
- LMArena是加州大学伯克利分校推出的AI模型匿名评测平台。通过盲测投票机制,用户可对比不同大模型回答并生成实时排行榜,助力开发者优化模型及用户选择最佳AI工具。
- 687次使用
-
- HELM
- 深入了解斯坦福推出的HELM(Holistic Evaluation of Language Models)大模型评测体系。本文解析其核心功能、安装配置步骤及应用场景,涵盖准确性、公平性、鲁棒性等多维度指标,助力开发者全面优化语言模型性能。
- 649次使用
-
- MMBench
- MMBench是由上海人工智能实验室等机构联合推出的多模态基准测试平台,提供细粒度能力评估、大规模数据集及VLMEvalKit工具。本文详细介绍其核心功能、安装使用方法及应用场景,助力开发者全面评估多模态模型性能。
- 477次使用
-
- JavaScript函数定义及示例详解
- 2025-05-11 502浏览
-
- 智能体安全引领产业升级——国内AI安全产品市场深度分析
- 2026-08-21 501浏览
-
- CSS变量简化按钮悬停效果技巧
- 2026-05-31 501浏览
-
- JavaScript符号类型详解与应用
- 2026-05-31 501浏览
-
- HTML剪贴板复制粘贴怎么用
- 2026-05-26 501浏览

