在前端开发中,当页面跳转时取消当前页面未完成的请求是一个常见的需求。以下是几种主流框架的解决方案,结合通用的 AbortController
API 实现这一功能。
1. 使用 AbortController
(通用方案)
AbortController
是浏览器原生提供的 API,可以取消未完成的异步请求(如 fetch
或 XMLHttpRequest
)。以下是核心思路:
步骤:
- 创建
AbortController
实例,并将其与页面或组件关联。 - 在请求时传递
signal
,确保请求可以被取消。 - 在页面跳转或卸载时调用
abort()
,取消所有未完成的请求。
示例代码(通用 JavaScript):
// 创建全局或页面级的 AbortController
const controller = new AbortController();
const signal = controller.signal;
// 发送请求时传入 signal
fetch('/api/data', { signal })
.then(response => response.json())
.then(data => console.log(data))
.catch(error => {
if (error.name === 'AbortError') {
console.log('请求已取消');
} else {
console.error('请求失败:', error);
}
});
// 页面跳转时调用 abort()
// 例如在组件卸载时(React/Vue)或路由守卫中:
controller.abort();
2. React 中的实现
在 React 中,可以通过 useEffect
的清理函数或路由守卫(如 react-router
)来实现。
方案 1:通过 useEffect
清理
import { useEffect, useRef } from 'react';
function MyComponent() {
const controller = useRef(new AbortController());
useEffect(() => {
const signal = controller.current.signal;
// 发送请求
fetch('/api/data', { signal })
.then(response => response.json())
.then(data => console.log(data))
.catch(error => {
if (error.name === 'AbortError') return;
console.error(error);
});
// 组件卸载时取消请求
return () => {
controller.current.abort();
};
}, []);
return <div>Content...</div>;
}
方案 2:通过 react-router
导航守卫
// 在路由配置中使用导航守卫
import { createBrowserRouter, Navigate } from 'react-router-dom';
const router = createBrowserRouter([
{
path: '/page',
element: <MyComponent />,
loader: async ({ signal }) => {
const response = await fetch('/api/data', { signal });
return response.json();
},
// 导航时自动取消未完成的请求
},
]);
3. Vue 中的实现
在 Vue 中,可以使用 onUnmounted
钩子或 Vue Router 的导航守卫。
方案 1:通过 onUnmounted
钩子
<template>
<div>Content...</div>
</template>
<script setup>
import { onUnmounted, ref } from 'vue';
const controller = ref(new AbortController());
const signal = controller.value.signal;
// 发送请求
fetch('/api/data', { signal })
.then(response => response.json())
.then(data => console.log(data))
.catch(error => {
if (error.name === 'AbortError') return;
console.error(error);
});
// 组件卸载时取消请求
onUnmounted(() => {
controller.value.abort();
});
</script>
方案 2:通过 Vue Router 导航守卫
// router/index.js
import { createRouter, createWebHistory } from 'vue-router';
const router = createRouter({
history: createWebHistory(),
routes: [
{
path: '/page',
component: MyComponent,
beforeEnter: (to, from, next) => {
// 在进入新路由前,取消当前页面的请求
// 需要将 controller 存储在全局或组件中
if (currentController) {
currentController.abort();
}
next();
},
},
],
});
4. 使用 Axios 的 CancelToken
(替代方案)
如果使用 Axios,可以通过 CancelToken
实现类似功能:
示例代码:
// 创建 CancelToken 源
const CancelToken = axios.CancelToken;
let cancel;
// 发送请求
axios.get('/api/data', {
cancelToken: new CancelToken(function executor(c) {
cancel = c;
})
})
.catch(thrown => {
if (axios.isCancel(thrown)) {
console.log('请求已取消:', thrown.message);
} else {
// 处理错误
}
});
// 页面跳转时取消请求
cancel('页面跳转,取消请求');
5. 全局管理请求(推荐复杂场景)
对于复杂应用,可以维护一个全局的请求管理器:
请求标识收集
在请求拦截器中为每个请求创建唯一标识(如 cancelToken
或 AbortController
),并将取消函数存储到全局容器(如 Vuex、Redux 或独立事件总线)中。
// Axios 拦截器示例(Vue/React 通用)
service.interceptors.request.use(config => {
const source = axios.CancelToken.source();
config.cancelToken = source.token;
store.commit('addRequest', source.cancel); // 存储取消函数到全局状态
return config;
});
路由跳转触发清理
在路由守卫(Vue Router)或组件卸载钩子(React)中遍历全局容器,执行所有未完成请求的取消操作。
// Vue Router 守卫示例
router.beforeEach((to, from, next) => {
store.state.requestList.forEach(cancel => cancel('Route changed')); // 取消所有请求
store.commit('clearRequests'); // 清空容器
next();
});
6.技术栈差异化实现
框架 | 实现方案 | 代码示例 | |
---|---|---|---|
Vue | 通过 Vuex 存储请求取消函数,结合 router.beforeEach 触发清理 | store.commit('pushPmsStack', cancel) + 路由守卫遍历清理 | |
React | 使用全局事件总线(如 eventBus )存储请求标识,在组件卸载时执行清理 | eventBus.cancelTokenList.push(cancel) + useEffect 清理函数 |
7.通用代码实现(Axios 适配)
1.创建可取消请求实例
// 创建 Axios 实例并配置拦截器
const service = axios.create();
let pendingRequests = new Map(); // 使用 Map 存储请求标识
service.interceptors.request.use(config => {
const requestKey = `${config.method}_${config.url}`;
const controller = new AbortController();
config.signal = controller.signal;
pendingRequests.set(requestKey, controller.abort); // 存储取消函数
return config;
});
2.路由跳转时批量取消
// 路由跳转逻辑(通用)
const handleRouteChange = () => {
pendingRequests.forEach(abort => abort('Navigation occurred')); // 执行所有取消操作
pendingRequests.clear(); // 清空存储
};