通常在SPA应用中大多数情况路由跳转不会进行刷新整个页面,这样就会导致当前页面请求未处理完成而抛出错误异常。
并且这个需求是离不开SPA应用的,在写开源项目时正好有这个需求,分享下我的实现思路。
以react为示例,其他框架异同。
先看看如何取消单个请求
取消单个请求非常简单,只需要配置cancelToken参数。
const CancelToken = axios.CancelToken;
let cancel;
axios.post('/example', {
cancelToken: new CancelToken(c => {
cancel = c;
})
});
// 取消请求
cancel();
取消所有请求
说下我的思路:将所有取消请求token存储在一个数组集合中,当路由发生变化时将集合所有token遍历取消再将集合置空。
实现方法如下:
0x01:
index.html
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8" />
<script>
// 为了方便声明在全局环境中
var axiosCancelTokenStore = [];
</script>
</head>
<body>
<div id="root"></div>
</body>
</html>
0x02:
在拦截器中配置cancelToken参数
const CancelToken = axios.CancelToken;
// 请求处理
httpInstance.interceptors.request.use(function (config) {
// do something...
config.cancelToken = new CancelToken(cancel => {
// 将cancel追加到集合中存储
axiosCancelTokenStore.push(cancel);
});
return config;
}, function (error) {
return Promise.reject(error);
});
// 响应处理
httpInstance.interceptors.response.use(function (res) {
// do something...
return res;
}, function (error) {
// 判断请求是否已取消
if (axios.isCancel(error)) {
// 返回一个空Promise,将Error Handler终止传递,这一步很重要
return new Promise(() => {});
}
return Promise.reject(error);
});
0x03:
由于react不像angular / vue有路由守卫,通常情况下需要自己单独封装一个路由组件。
大致如下,当路由渲染前取消所有请求
import React from 'react';
import { Switch, Route, Redirect } from 'react-router-dom';
const RouteComponent = function ({
component: Component,
...rest
}) {
// 取消所有http请求
if (Array.isArray(axiosCancelTokenStore)) {
// 从集合中遍历取消
axiosCancelTokenStore.forEach(cancel => {
if (typeof cancel === 'function') {
cancel();
}
});
// 将集合置空
axiosCancelTokenStore = [];
}
return (
<Route render={props => {
return (
<Component {...props} {...rest}></Component>
)
}} />
)
};
当前页面请求未完成时进入下一个路由会出现如下:
说明请求已被取消,大功告成!
但是还有一种情况,有些请求用于初始化操作是不能被取消的, 这个时候可以转换下思路,在请求头添加个标识符即可, 代码如下:
请求接口
axios.post('/example', null, {
headers: {
cancelRequest: false
}
});
将之前的拦截器改造下
const CancelToken = axios.CancelToken;
// 请求处理
httpInstance.interceptors.request.use(function (config) {
// do something...
// 判断请求头参数 cancelRequest 是否不为false
if (config.headers.cancelRequest !== false) {
config.cancelToken = new CancelToken(cancel => {
axiosCancelTokenStore.push(cancel);
});
}
return config;
}, function (error) {
return Promise.reject(error);
});
// 响应处理
httpInstance.interceptors.response.use(function (res) {
// do something...
return res;
}, function (error) {
// 判断请求是否已取消
if (axios.isCancel(error)) {
// 返回一个空Promise,将Error Handler终止传递。
return new Promise(() => {});
}
return Promise.reject(error);
});
完整代码
https://github.com/xjh22222228/tomato-work/blob/master/src/utils/http.ts
https://github.com/xjh22222228/tomato-work/blob/master/src/components/private-route/index.tsx#L21