Fetch AbortController 如何取消超时请求
来源:17golang原创
时间:2026-09-12 16:48:56 108浏览 收藏
给前端 fetch 加超时,不是给 Promise 外面套一个 setTimeout 就结束了。真正要中止网络请求,需要把 AbortSignal 传进 fetch 的 signal 选项;超时后由信号触发取消。现代浏览器可以直接使用 AbortSignal.timeout(ms),并在捕获异常时把 TimeoutError 和用户主动取消产生的 AbortError 分开处理。
- 每次请求创建新的
AbortController或超时信号,已终止的信号不能复用。 AbortSignal.timeout(5000)会在超时后终止fetch,但不会把 HTTP 500 自动变成异常。- 捕获异常时优先判断
TimeoutError、AbortError,最后再处理网络错误或其他异常。 - 需要兼容较旧浏览器或明确清理定时器时,可用
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();

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() 超时通常以名称为 TimeoutError 的 DOMException 拒绝;调用控制器的 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.aborted 和 signal.reason。但不要用“进入了 catch”作为超时判断,因为 DNS、CORS、断网和解析失败同样可能进入 catch。

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/5xx | response.ok | 按业务状态展示服务端错误 |
| 断网、CORS、解析异常 | 其他异常 | 保留原始错误并进入通用故障处理 |
上线前至少检查五件事:信号是否每次请求独立创建;signal 是否真的传入 fetch;响应状态是否单独判断;响应体读取是否仍在取消保护范围内;自定义事件和定时器是否在 finally 中清理。这样处理后,Fetch 的超时、取消和网络失败就不会再被一条模糊的“请求失败”吞掉。
常见问题
AbortSignal.timeout 的参数单位是什么?
单位是毫秒,例如 AbortSignal.timeout(5000) 表示约 5 秒的活动时间预算。
fetch 返回 500 会触发 catch 吗?
不会仅因为状态码触发。先检查 response.ok 或 response.status,再按业务抛出异常。
一个 AbortController 能给多个请求使用吗?
可以让多个请求共享同一个取消动作,但控制器一旦 abort,所有关联请求都会被终止;已终止的 signal 不适合启动新请求。
AbortSignal.any 能告诉我是谁触发取消吗?
它适合统一取消入口,但不能可靠区分最终来源。需要统计来源时,额外维护用户取消标记和超时标记。
-
502 收藏
-
501 收藏
-
501 收藏
-
501 收藏
-
501 收藏
-
491 收藏
-
文章 · 前端 | 3小时前 | javascript · web components · custom elements · 前端排错 · Web Components Custom Elements attributeChangedCallback observedAttributes233 收藏
-
442 收藏
-
304 收藏
-
483 收藏
-
文章 · 前端 | 1天前 | 前端 · web components · 生命周期 · 自定义元素 · document Web Components Custom Elements adoptedCallback adoptNode importNode382 收藏
-
215 收藏
-
154 收藏
-
147 收藏
-
107 收藏
-
221 收藏
-
199 收藏
-
- 前端进阶之JavaScript设计模式
- 设计模式是开发人员在软件开发过程中面临一般问题时的解决方案,代表了最佳的实践。本课程的主打内容包括JS常见设计模式以及具体应用场景,打造一站式知识长龙服务,适合有JS基础的同学学习。
- 立即学习 543次学习
-
- GO语言核心编程课程
- 本课程采用真实案例,全面具体可落地,从理论到实践,一步一步将GO核心编程技术、编程思想、底层实现融会贯通,使学习者贴近时代脉搏,做IT互联网时代的弄潮儿。
- 立即学习 516次学习
-
- 简单聊聊mysql8与网络通信
- 如有问题加微信:Le-studyg;在课程中,我们将首先介绍MySQL8的新特性,包括性能优化、安全增强、新数据类型等,帮助学生快速熟悉MySQL8的最新功能。接着,我们将深入解析MySQL的网络通信机制,包括协议、连接管理、数据传输等,让
- 立即学习 500次学习
-
- JavaScript正则表达式基础与实战
- 在任何一门编程语言中,正则表达式,都是一项重要的知识,它提供了高效的字符串匹配与捕获机制,可以极大的简化程序设计。
- 立即学习 487次学习
-
- 从零制作响应式网站—Grid布局
- 本系列教程将展示从零制作一个假想的网络科技公司官网,分为导航,轮播,关于我们,成功案例,服务流程,团队介绍,数据部分,公司动态,底部信息等内容区块。网站整体采用CSSGrid布局,支持响应式,有流畅过渡和展现动画。
- 立即学习 485次学习