深入理解请求取消机制:从原生到框架实现
1.为什么需要请求取消?
在前端开发中,经常会遇到需要取消请求的场景:
- 用户快速切换页面或标签
- 搜索框实时搜索时,需要取消上一次未完成的请求
- 防止重复提交
- 优化性能,避免无效请求占用资源
2.原生实现方案
2.1 XMLHttpRequest
早期使用 XMLHttpRequest 时,可以通过 abort() 方法取消请求:
js
const xhr = new XMLHttpRequest();
xhr.open('GET', 'api/data');
xhr.send();
// 取消请求
xhr.abort();2.2 Fetch API
现代浏览器提供了 AbortController API:
js
const controller = new AbortController();
const signal = controller.signal;
fetch('api/data', { signal })
.then(response => response.json())
.catch(err => {
if (err.name === 'AbortError') {
console.log('请求已取消');
}
});
// 取消请求
controller.abort();3.Axios 的取消机制
Axios 提供了两种取消请求的方式:
3.1 CancelToken(已废弃)
js
const CancelToken = axios.CancelToken;
const source = CancelToken.source();
axios.get('api/data', {
cancelToken: source.token
});
// 取消请求
source.cancel('请求已取消');3.2 AbortController(推荐)
js
const controller = new AbortController();
axios.get('api/data', {
signal: controller.signal
});
// 取消请求
controller.abort();4. 实际应用场景
4.1 搜索防抖
js
const SearchComponent = () => {
const [searchTerm, setSearchTerm] = useState('');
const controller = useRef(null);
useEffect(() => {
return () => {
if (controller.current) {
controller.current.abort();
}
};
}, []);
const handleSearch = async (value) => {
if (controller.current) {
controller.current.abort();
}
controller.current = new AbortController();
try {
const result = await axios.get(`/api/search?q=${value}`, {
signal: controller.current.signal
});
// 处理结果
} catch (error) {
if (axios.isCancel(error)) {
console.log('搜索请求已取消');
}
}
};
return (
<input
value={searchTerm}
onChange={e => {
setSearchTerm(e.target.value);
handleSearch(e.target.value);
}}
/>
);
};4.2 路由切换取消
js
const useRequestCancel = () => {
const controllerRef = useRef(null);
useEffect(() => {
return () => {
if (controllerRef.current) {
controllerRef.current.abort();
}
};
}, []);
const request = async (url) => {
if (controllerRef.current) {
controllerRef.current.abort();
}
controllerRef.current = new AbortController();
return axios.get(url, {
signal: controllerRef.current.signal
});
};
return { request };
};4.3 自定义控制取消
手动实现取消请求,比如说对于cardsType发生变化的时候可以手动取消
js
// net.js
export let cancelRequest;
export const getCards = ({ type, page_num, task_type, needCancel = true }) => {
let cancelRequest;
if (needCancel) {
cancelRequest = axios.CancelToken.source();
}
try {
return axiosClient.post(
`${BASE_URL}/task/cards`,
{ type, page_num, task_type },
{
cancelToken: needCancel ? cancelRequest.token : undefined,
}
)
.catch((error) => {
if (axios.isCancel(error)) {
console.log('请求已取消');
} else {
console.error(error);
}
});
} catch (error) {
console.error(error);
}
};
// 组件中
useDebounceEffect(() => {
// 在切换 cardsType 时主动取消上一次请求
if (cancelRequest) {
cancelRequest.cancel('CardType changed');
}
// ... existing code ...
}, [cardsType], 100)5. 请求取消的本质与限制
5.1 前端取消请求的本质
需要明确一个重要概念:前端的请求取消实际上并不能真正"取消"已经发出的请求。当我们调用取消方法时:
- 如果请求还未发出,则可以真正地取消
- 如果请求已经发出:
- 请求仍会到达服务器并执行相应的操作
- 只是客户端不再等待响应,也不会执行后续的处理逻辑
5.2 典型场景分析
js
// 以添加用户为例
const addUser = async (userData) => {
const controller = new AbortController();
try {
await axios.post('/api/users', userData, {
signal: controller.signal
});
console.log('用户添加成功');
} catch (error) {
if (axios.isCancel(error)) {
console.log('请求已取消');
return;
}
console.error('添加用户失败:', error);
}
// 取消请求
controller.abort();
};在上面的例子中:
- 如果在请求发出前取消,则请求不会发送到服务器
- 如果在请求已发出后取消:
- 服务器依然会处理添加用户的操作
- 客户端不会执行后续的
console.log('用户添加成功')
5.3 最佳实践建议
对于写操作(POST/PUT/DELETE):
- 应谨慎使用取消机制
- 建议实现服务端幂等性来处理可能的重复请求
- 考虑添加事务回滚机制
对于读操作(GET):
- 可以放心使用取消机制
- 适用于搜索、列表加载等场景
- 有助于减少无效请求和资源占用
关键业务场景:
- 建议添加请求状态追踪
- 实现完整的错误处理机制
- 考虑添加用户提示,告知操作可能已经执行