我第一次接触 AbortController 是在一个深夜调试项目的时候。当时我向 HolySheep AI 发送了一个很长的请求,结果用户不小心点了返回键,页面跳转了,但请求还在后台跑,不仅浪费了用户流量,还导致页面逻辑混乱。从那以后,我就养成了给所有 API 请求加上取消机制的习惯。今天我就把这套经验分享给你,保证你看完就能上手。
什么是 AbortController?为什么要取消请求?
你可以把 AbortController 想象成一个「遥控器」。当你按电视遥控器的关闭键,电视就会停止播放。AbortController 就是你给浏览器或 Node.js 发送的「关闭指令」,让它停止正在运行的请求。
在实际开发中,取消请求的场景非常常见:
- 用户快速切换页面或标签,请求还没返回就跳转了
- 用户疯狂点击搜索按钮,产生了很多重复请求
- 请求时间过长(比如超过 10 秒),用户不想等了
- 用户填完表单提交后发现填错了,想立刻停止
根据 HolySheep AI 的实测数据,国内直连延迟可以控制在 50ms 以内,但如果是复杂的推理任务(如 GPT-4.1),单个请求可能需要几十秒。合理使用取消机制,可以为用户节省约 30% 的等待时间和流量费用。
AbortController 基本用法:三行代码搞定
别被这个名字吓到,其实它用起来超级简单。核心就三步:创建控制器、关联请求、触发取消。
第一步:创建 AbortController 实例
// 创建一个「遥控器」
const controller = new AbortController();
第二步:把遥控器和请求绑定
// 把信号线连接到请求上
const response = await fetch('https://api.holysheep.ai/v1/chat/completions', {
method: 'POST',
headers: {
'Content-Type': 'application/json',
'Authorization': 'Bearer YOUR_HOLYSHEEP_API_KEY'
},
body: JSON.stringify({
model: 'gpt-4.1',
messages: [{ role: 'user', content: '你好' }]
}),
signal: controller.signal // 关键!这行代码把遥控器和请求连起来了
});
第三步:调用 cancel() 方法停止请求
// 按下遥控器的关闭键
controller.abort();
就这么简单!当你调用 controller.abort() 时,fetch 请求会立即被取消,浏览器会抛出一个 AbortError 错误,你可以通过 try-catch 来捕获它。
实战案例:取消 HolySheep AI 对话请求
现在让我们来一个完整的实战案例。假设你要做一个聊天应用,用户可以随时取消正在生成的回复。
<!DOCTYPE html>
<html lang="zh-CN">
<head>
<meta charset="UTF-8">
<title>AI 聊天演示</title>
</head>
<body>
<h1>HolySheep AI 聊天演示</h1>
<button id="startBtn">开始对话</button>
<button id="cancelBtn" disabled>取消请求</button>
<div id="output"></div>
<script>
let currentController = null;
async function sendChatRequest() {
// 禁用开始按钮,启用取消按钮
document.getElementById('startBtn').disabled = true;
document.getElementById('cancelBtn').disabled = false;
document.getElementById('output').innerHTML = '正在等待回复...';
// 每次发送请求都创建新的 controller
currentController = new AbortController();
try {
const response = await fetch('https://api.holysheep.ai/v1/chat/completions', {
method: 'POST',
headers: {
'Content-Type': 'application/json',
'Authorization': 'Bearer YOUR_HOLYSHEEP_API_KEY'
},
body: JSON.stringify({
model: 'gpt-4.1',
messages: [
{ role: 'system', content: '你是一个有帮助的助手' },
{ role: 'user', content: '请用100字介绍一下人工智能的发展历史' }
],
max_tokens: 500
}),
signal: currentController.signal
});
const data = await response.json();
if (data.choices && data.choices[0]) {
document.getElementById('output').innerHTML =
'<p>AI 回复:</p><p>' + data.choices[0].message.content + '</p>';
}
} catch (error) {
if (error.name === 'AbortError') {
document.getElementById('output').innerHTML = '⏹️ 请求已被用户取消';
} else {
document.getElementById('output').innerHTML = '❌ 发生错误:' + error.message;
}
} finally {
document.getElementById('startBtn').disabled = false;
document.getElementById('cancelBtn').disabled = true;
currentController = null;
}
}
function cancelRequest() {
if (currentController) {
currentController.abort();
}
}
document.getElementById('startBtn').addEventListener('click', sendChatRequest);
document.getElementById('cancelBtn').addEventListener('click', cancelRequest);
</script>
</body>
</html>
在这个例子中,我每次点击「开始对话」都会创建一个新的 AbortController 实例。这样做的好处是:每次请求都是独立的,取消操作不会影响到其他请求。HolySheep AI 支持微信/支付宝充值,汇率仅需 ¥7.3 = $1(官方汇率),注册即送免费额度,非常适合新手练手。
高级用法:超时自动取消
有时候用户没有手动取消,但请求本身运行太久了,这时候我们需要设置「超时」来自动取消请求。
/**
* 带超时功能的请求封装
* @param {string} url - API 地址
* @param {object} options - fetch 选项
* @param {number} timeoutMs - 超时毫秒数,默认 30 秒
*/
async function fetchWithTimeout(url, options = {}, timeoutMs = 30000) {
const controller = new AbortController();
// 设置定时器,30 秒后自动取消
const timeoutId = setTimeout(() => {
controller.abort();
console.log('⏰ 请求超时,已自动取消');
}, timeoutMs);
try {
const response = await fetch(url, {
...options,
signal: controller.signal
});
// 清除定时器,因为请求已经完成了
clearTimeout(timeoutId);
return response;
} catch (error) {
clearTimeout(timeoutId); // 也要清除,防止内存泄漏
if (error.name === 'AbortError') {
if (error.message === 'Aborted') {
throw new Error('请求超时(超过 ' + (timeoutMs / 1000) + ' 秒)');
}
}
throw error;
}
}
// 使用示例:向 HolySheep AI 发送请求,10 秒超时
async function chatWithTimeout() {
try {
const response = await fetchWithTimeout(
'https://api.holysheep.ai/v1/chat/completions',
{
method: 'POST',
headers: {
'Content-Type': 'application/json',
'Authorization': 'Bearer YOUR_HOLYSHEEP_API_KEY'
},
body: JSON.stringify({
model: 'gemini-2.5-flash', // 这个模型速度快,适合需要超时的场景
messages: [{ role: 'user', content: '你好' }]
})
},
10000 // 10 秒超时
);
const data = await response.json();
console.log('回复:', data.choices[0].message.content);
} catch (error) {
console.error('出错了:', error.message);
}
}
chatWithTimeout();
我自己在项目中使用这个封装函数时,通常会把超时时间设为 30 秒到 60 秒之间。对于简单的对话请求,30 秒足够了;如果是需要复杂推理的请求(比如使用 Claude Sonnet 4.5,它的价格是 $15/MTok),可以考虑延长到 60 秒。
AbortController 完整 API 详解
AbortController 其实只有两个属性和方法:
- signal:一个 AbortSignal 对象,你可以把它理解为「信号线」。把它传给 fetch 的
signal参数,请求就会响应取消操作。 - abort():一个方法,调用它就会发送取消信号,所有监听这个 signal 的请求都会被中断。
AbortSignal 还有一个很实用的功能:你可以用它来监听取消事件。
const controller = new AbortController();
// 监听取消事件(虽然实际场景中很少用)
controller.signal.addEventListener('abort', () => {
console.log('请求被取消了!');
// 在这里可以做清理工作,比如关闭 loading 动画等
});
// 或者用 onabort 属性
controller.signal.onabort = () => {
console.log('另一种写法,同样可以监听取消事件');
};
// 触发取消
controller.abort();
// 输出:请求被取消了!/ 另一种写法,同样可以监听取消事件
常见报错排查
错误一:请求已经被取消,但代码没有捕获错误
// ❌ 错误写法:没有 try-catch 包裹
const response = await fetch(url, { signal: controller.signal });
const data = await response.json();
// ✅ 正确写法:加上错误处理
try {
const response = await fetch(url, { signal: controller.signal });
const data = await response.json();
console.log(data);
} catch (error) {
if (error.name === 'AbortError') {
console.log('请求被取消了');
} else {
console.error('其他错误:', error);
}
}
报错信息:Uncaught (in promise) DOMException: The user aborted a request.
解决方法:必须在 fetch 调用外层包裹 try-catch,或者使用 .catch() 来处理取消错误。
错误二:重复调用 abort() 导致的问题
// ❌ 错误写法:多次调用 abort
const controller = new AbortController();
fetch(url, { signal: controller.signal }); // 请求 1
controller.abort(); // 取消请求 1
fetch(url, { signal: controller.signal }); // 请求 2 - 这里可能会出问题!
// ✅ 正确写法:一个请求对应一个 controller
const controller1 = new AbortController();
fetch(url, { signal: controller1.signal }); // 请求 1
controller1.abort(); // 取消请求 1
const controller2 = new AbortController();
fetch(url, { signal: controller2.signal }); // 请求 2 - 独立的 controller
报错信息:InvalidStateError: The request has already been aborted.
解决方法:每次发起新请求时,都要创建新的 AbortController 实例,不要复用旧的。
错误三:使用 AbortController.with 指定超时时间时忘记兼容处理
// ❌ 错误写法:直接使用 AbortSignal.timeout(旧浏览器不兼容)
const response = await fetch(url, {
signal: AbortSignal.timeout(5000) // 一些旧版浏览器不支持这个 API!
});
// ✅ 正确写法:自己封装超时函数
function createTimeoutSignal(ms) {
const controller = new AbortController();
setTimeout(() => controller.abort(), ms);
return controller.signal;
}
const response = await fetch(url, {
signal: createTimeoutSignal(5000)
});
报错信息:TypeError: AbortSignal.timeout is not a function
解决方法:如果你需要兼容旧版浏览器,建议使用上面自己封装的超时函数,而不是依赖 AbortSignal.timeout() 这个较新的 API。
错误四:取消请求后没有清理定时器导致内存泄漏
// ❌ 错误写法:没有清理定时器
function fetchWithTimeout(url, options, timeoutMs) {
const controller = new AbortController();
setTimeout(() => {
controller.abort();
}, timeoutMs);
// 如果请求 3 秒就完成了,定时器还在内存里!
return fetch(url, { signal: controller.signal });
}
// ✅ 正确写法:使用 finally 确保清理
function fetchWithTimeout(url, options, timeoutMs) {
const controller = new AbortController();
const timeoutId = setTimeout(() => controller.abort(), timeoutMs);
return fetch(url, { ...options, signal: controller.signal })
.finally(() => clearTimeout(timeoutId)); // 请求结束后清除定时器
}
报错信息:长时间运行后浏览器变卡,可能出现内存泄漏警告。
解决方法:使用 .finally() 或在 try-catch 的 finally 块中清理定时器。
在 React 和 Vue 中使用 AbortController
很多前端开发者是在 React 或 Vue 项目中使用 API 请求的,这里我补充一下这两个框架的用法。
// React Hook 版本
import { useState, useEffect } from 'react';
function ChatComponent() {
const [messages, setMessages] = useState([]);
const [loading, setLoading] = useState(false);
const [controller, setController] = useState(null);
const sendMessage = async (content) => {
// 创建新的 controller
const newController = new AbortController();
setController(newController);
setLoading(true);
try {
const response = await fetch('https://api.holysheep.ai/v1/chat/completions', {
method: 'POST',
headers: {
'Content-Type': 'application/json',
'Authorization': 'Bearer YOUR_HOLYSHEEP_API_KEY'
},
body: JSON.stringify({
model: 'deepseek-v3.2', // 价格便宜,只需 $0.42/MTok
messages: [...messages, { role: 'user', content }]
}),
signal: newController.signal
});
const data = await response.json();
setMessages(prev => [...prev, data.choices[0].message]);
} catch (error) {
if (error.name === 'AbortError') {
console.log('请求已取消');
}
} finally {
setLoading(false);
}
};
const cancelRequest = () => {
if (controller) {
controller.abort();
}
};
// 组件卸载时也要取消请求
useEffect(() => {
return () => {
if (controller) {
controller.abort();
}
};
}, [controller]);
return (
<div>
<button onClick={() => sendMessage('你好')} disabled={loading}>
发送
</button>
<button onClick={cancelRequest} disabled={!loading}>
取消
</button>
</div>
);
}
我强烈建议在组件卸载的 useEffect cleanup 函数中也调用 abort(),这样可以防止组件卸载后状态更新导致的「Cannot update unmounted component」警告。
总结
AbortController 是现代 JavaScript 中处理异步请求取消的标准方案,它的核心优势是:
- API 设计简洁,三行代码即可实现取消功能
- 原生支持,不需要安装任何第三方库
- 与 Promise完美配合,可以优雅地处理取消后的逻辑
- 支持超时自动取消,可以防止请求卡死
在 AI API 调用场景中,合理使用 AbortController 尤为重要。以 HolySheep AI 为例,它提供了国内直连 <50ms 的超低延迟,但复杂模型(如 GPT-4.1,$8/MTok)的推理时间仍然较长。有了取消机制,用户可以随时停止不满意的请求,避免浪费额度。
建议你在所有向外发起的 API 请求中,都加入 AbortController 支持,尤其是:搜索功能、实时聊天、文件上传等用户可能频繁操作的场景。养成这个好习惯,能让你的应用体验提升一个档次。