在Web开发中,处理网络请求时经常遇到用户快速切换页面或在搜索框中连续输入的情况。如果不及时取消已发出的旧请求,不仅会浪费服务器资源和带宽,还可能导致“竞态条件”,即旧请求的数据晚于新请求返回,覆盖了正确的界面内容。AbortController 是现代浏览器提供的原生 API,专门用于取消如 fetch 这样的异步操作。
- 构建 基础的取消请求逻辑
AbortController 的工作机制像一个信号发射器。你需要创建一个控制器实例,将其关联的 signal 传递给 fetch 请求,当需要停止时,只需告诉控制器停止,fetch 就会立即中断。
编写 以下代码来体验最基础的取消操作:
// 1. 创建一个 AbortController 实例
const controller = new AbortController();
// 2. 获取关联的 signal 对象
const signal = controller.signal;
// 3. 发起 fetch 请求,并将 signal 传递给配置项
fetch('https://api.example.com/data', { signal })
.then(response => response.json())
.then(data => console.log('请求成功:', data))
.catch(error => {
// 4. 捕获错误,判断是否为主动取消导致的错误
if (error.name === 'AbortError') {
console.log('请求已被用户主动取消');
} else {
console.error('请求发生错误:', error);
}
});
// 模拟:在 500ms 后取消请求
setTimeout(() => {
controller.abort();
}, 500);
执行上述代码,如果网络请求耗时超过 500 毫秒,fetch 会抛出一个 AbortError,进入 catch 分支。
- 应用 在搜索框防抖场景(解决竞态问题)
这是 AbortController 最经典的使用场景。当用户在搜索框快速输入字符时,每一次按键都会触发请求。若不处理,前序请求的响应可能会在后续请求响应之后到达,导致显示结果与用户当前输入不符。
遵循 以下步骤构建防抖取消机制:
- 声明 一个外部变量
currentController用于存储当前活跃的控制器。 - 定义 搜索函数,在每次发起新请求前,检查
currentController是否存在。 - 调用
currentController.abort()取消上一次未完成的请求。 - 赋值 新的
AbortController实例给currentController,并发起新的fetch。
实现 代码如下:
let currentController = null;
function handleSearchInput(query) {
// 1. 如果存在正在进行的请求,先取消它
if (currentController) {
currentController.abort();
}
// 2. 创建新的控制器
currentController = new AbortController();
const signal = currentController.signal;
// 3. 发起新请求
fetch(`/api/search?q=${query}`, { signal })
.then(res => res.json())
.then(data => {
// 仅处理成功的数据
updateUI(data);
})
.catch(error => {
// 忽略取消操作带来的错误,避免控制台报错干扰
if (error.name !== 'AbortError') {
console.error('搜索失败:', error);
}
});
}
- 设置 请求自动超时
除了手动取消,还可以利用 AbortController 强制执行超时策略。例如,如果服务器在 5 秒内没有响应,直接中断请求以提升用户体验。
组合 setTimeout 和 controller.abort() 来实现:
function fetchWithTimeout(url, timeout = 5000) {
const controller = new AbortController();
const signal = controller.signal;
// 设置定时器,超时后调用 abort
const timeoutId = setTimeout(() => {
controller.abort();
}, timeout);
return fetch(url, { signal })
.then(response => {
// 请求成功,清除定时器
clearTimeout(timeoutId);
return response;
})
.catch(error => {
// 清除定时器(防止内存泄漏)
clearTimeout(timeoutId);
// 抛出错误以便上层处理
throw error;
});
}
// 使用示例
fetchWithTimeout('https://api.example.com/slow', 2000)
.then(console.log)
.catch(error => {
if (error.name === 'AbortError') {
console.error('请求超时,服务器响应过慢');
}
});
- 识别 与处理 AbortError
当 controller.abort() 被调用时,fetch Promise 会立即 reject 并抛出一个 DOMException,其 name 属性固定为 'AbortError'。
注意:不要在 catch 块中把取消操作当作普通的网络错误来处理(例如不要弹出“网络连接失败”的提示框),这会严重破坏用户体验。务必使用 error.name === 'AbortError' 进行过滤判断。
检查 你的错误处理逻辑是否符合以下规范:
try {
const response = await fetch(url, { signal });
const data = await response.json();
// 处理数据...
} catch (error) {
if (error.name === 'AbortError') {
// 静默处理或显示“已取消”状态
return;
}
// 处理其他真正的错误
showErrorToast('加载失败,请重试');
}
暂无评论,快来抢沙发吧!