vue项目中关于axios取消请求的解决方案
在axios速读中,我们了解到axios请求的取消方法。
const CancelToken = axios.CancelToken;
//请求前拦截器
axios.interceptors.request.use(function(config) {
//添加取消token
const source = CancelToken.source();
config.cancelToken = source.token;
//取消请求
source.cancel("取消请求");
return config;
}, function(error) {
// 对请求错误做些什么
return Promise.reject(error);
});
但是在vue的项目中,取消请求是分很多种情况的,为此在掘金看了一篇关于axios取消请求的文章,非常有道理,我也是用自己的笔记记起来,内容可能大相径庭,有兴趣可以看看这篇文章:《axios切换路由取消指定请求与取消重复请求并存方案》
axios需要取消请求的一些情况
- 重复请求同一个api地址
- vue路由页切换,取消上一个路由还在发起或者等待返回的aixos请求
- 部分请求并不需要取消,比如全局性的api请求
基于以上三种情况,基本上涵盖了目前vue项目中遇到的一些情况。
如何实现 原理部分
其实也很简单易懂,我们在axios请求拦截器里面,使用一个数组来保存需要取消请求的cancel方法,但是为了识别是否为重复请求,我们需要一个key值去判断。
这个key值的组成:method+api地址
这样只要是同一个请求协议,请求同一个api地址,那么我们应该是取消上一个请求,发起本次请求
那么处理路由页切换,我们在路由守卫beforeEach
中,拿到这个数组,然后遍历,依次出发cancel方法。
但是这样的话难免会取消到不是这个路由页的请求,因为我们一开始就是将所有的aixos请求取消方法都传入了数组中保存,遍历的时候,所有的请求都会被我们取消。
所以,我们要设置一个配置属性,当这个属性为false时,我们就不在遍历中取消这个请求。
具体实现方法我们下面直接放代码:
取消重复请求
import axios from "axios";
//创建自定义axios示例
let api = axios.create({});
//取消请求的数组
let cancelToken = [];
//请求前拦截
api.interceptors.request.use(config => {
//获取请求协议+路径
const requestUrl = `${config.method} ${config.url}`;
//查找取消请求标识中是否存在相同的requestUrl
const cancelIndex = cancelToken.findIndex(item => {
return item.url === requestUrl;
});
//存在就说明重复了
if (cancelIndex > -1) {
//取消
cancelToken[cancelIndex].cancel();
//删除该标识
cancelToken.splice(cancelIndex, 1);
};
//当前请求添加取消方法
const CancelToken = axios.CancelToken;
const source = CancelToken.source();
config.cancelToken = source.token;
//传入本地取消请求
cancelToken.push({
url: requestUrl,
cancel: source.cancel,
});
return config;
}, error => Promise.reject(error))
这样每次axios发请求前都会判断有没有重复请求,有的话取消上一次,记录本次取消请求,再发出请求。
但是会有一个问题,比如,如果我这次请求已经完成了,用户再发一次请求获取数据, 相同的地址还是会被识别为重复请求,因为数据残留了,那个已完成的请求还在我们的cancelToken数组里面。
所以,我们需要在请求成功后删除cancelToken对应的记录。
成功后删除cancelToken中的记录
如果我们要在成功后判断是否已经在cancelToken存在了,我们用requestUrl
来判别,在aixos中,他对xhr进行了封装,我们获取到的response 并不是原生的response 对象,在response 对象里面,有一个config属性,这个属性是贯穿请求前后请求后的,也就是说,我们在请求前对config做的设置,请求后依旧可以通过config获取到。
这样我们将requestUrl
存放在config中,这样响应后,我们依旧知道,响应的这个请求是谁的请求。
于是乎:
import axios from "axios";
//创建自定义axios示例
let api = axios.create({});
//取消请求的数组
let cancelToken = [];
//请求前拦截
api.interceptors.request.use(config => {
//获取请求协议+路径
const requestUrl = `${config.method} ${config.url}`;
//查找取消请求标识中是否存在相同的requestUrl
const cancelIndex = cancelToken.findIndex(item => {
return item.url === requestUrl;
});
//存在就说明重复了
if (cancelIndex > -1) {
//取消
cancelToken[cancelIndex].cancel();
//删除该标识
cancelToken.splice(cancelIndex, 1);
};
//添加响应拦截中成功后删除该标识的判断条件
config.requestUrl = requestUrl;
//传入本地取消请求
cancelToken.push({
url: requestUrl,
cancel: source.cancel,
});
return config;
}, error => Promise.reject(error));
//axios响应拦截器
api.interceptors.response.use(response => {
//响应拦截器删除取消标识中已完成的请求
const cancelIndex = cancelToken.findIndex(item => {
return item.url === response.config.requestUrl
});
if (cancelIndex > -1) {
cancelToken.splice(cancelIndex, 1);
};
//其他自定义的操作
....
}, error => Promise.reject(error));
不管成功还是失败,只要这个请求响应了,我们就可以删除它的记录。
axios的响应拦截器中,如果返回的状态码不在2xx范围内,它会走error方法,所以,我们还需要考虑到在error中删除记录。
既然两边都要,我们可以把方法提取出来。
//响应拦截器删除取消标识中已完成的请求
const responseDeleteCancelToken = config => {
const cancelIndex = cancelToken.findIndex(item => {
return item.url === config.requestUrl
});
if (cancelIndex > -1) {
cancelToken.splice(cancelIndex, 1);
};
};
//axios响应拦截器
api.interceptors.response.use(response => {
//响应拦截器删除取消标识中已完成的请求
responseDeleteCancelToken(response.config)
//其他自定义的操作
....
}, error => {
//响应拦截器删除取消标识中已完成的请求
const response = error.response;
// 请求已发出,但服务器响应的状态码不在 2xx 范围内
if (response) {
//删除取消标识
responseDeleteCancelToken(response.config);
}
return Promise.reject(error);
});
这样,我们在两种状态中都对应做了处理。
补充一个小功能:
当我们cancel取消axios请求时,其实会出发响应拦截器的error方法,但是取消请求我们并不需要处罚catch方法的回调。
为了判断是否为取消的请求,axios提供了一个方法的,通过axios来判断error对象
axios.isCancel(error); //true or false
但是我们在vue项目里面,一般都是模块化axios,不可能每个需要请求api的vue文件里面都import引入axios来判断,所以,我们在响应拦截器里面对error添加一个属性用于判断是否为取消的请求错误对象。
//响应拦截器删除取消标识中已完成的请求
const responseDeleteCancelToken = config => {
const cancelIndex = cancelToken.findIndex(item => {
return item.url === config.requestUrl
});
if (cancelIndex > -1) {
cancelToken.splice(cancelIndex, 1);
};
};
//axios响应拦截器
api.interceptors.response.use(response => {
//响应拦截器删除取消标识中已完成的请求
responseDeleteCancelToken(response.config)
//其他自定义的操作
....
}, error => {
//响应拦截器删除取消标识中已完成的请求
const response = error.response;
// 请求已发出,但服务器响应的状态码不在 2xx 范围内
if (response) {
//删除取消标识
responseDeleteCancelToken(response.config);
}
//取消的请求给错误对象添加一个标识
if (axios.isCancel(error)) {
error.selfCancel = true;
}
return Promise.reject(error);
});
这样,我们在api请求的catch方法中,只要判断error.selfCancel
属性就能知道是不是取消请求触发的,是的话就跳过。
vue路由切换取消上一个路由请求
我们目前axios内容如下:
import axios from "axios";
//创建自定义axios示例
let api = axios.create({});
//取消请求的数组
let cancelToken = [];
//请求前拦截
api.interceptors.request.use(config => {
//获取请求协议+路径
const requestUrl = `${config.method} ${config.url}`;
//查找取消请求标识中是否存在相同的requestUrl
const cancelIndex = cancelToken.findIndex(item => {
return item.url === requestUrl;
});
//存在就说明重复了
if (cancelIndex > -1) {
//取消
cancelToken[cancelIndex].cancel();
//删除该标识
cancelToken.splice(cancelIndex, 1);
};
//添加响应拦截中成功后删除该标识的判断条件
config.requestUrl = requestUrl;
//传入本地取消请求
cancelToken.push({
url: requestUrl,
cancel: source.cancel,
});
return config;
}, error => Promise.reject(error));
//响应拦截器删除取消标识中已完成的请求
const responseDeleteCancelToken = config => {
const cancelIndex = cancelToken.findIndex(item => {
return item.url === config.requestUrl
});
if (cancelIndex > -1) {
cancelToken.splice(cancelIndex, 1);
};
};
//axios响应拦截器
api.interceptors.response.use(response => {
//响应拦截器删除取消标识中已完成的请求
responseDeleteCancelToken(response.config)
//其他自定义的操作
....
}, error => {
//响应拦截器删除取消标识中已完成的请求
const response = error.response;
// 请求已发出,但服务器响应的状态码不在 2xx 范围内
if (response) {
//删除取消标识
responseDeleteCancelToken(response.config);
}
//取消的请求给错误对象添加一个标识
if (axios.isCancel(error)) {
error.selfCancel = true;
}
return Promise.reject(error);
});
export { api, cancelToken };
我们讲cancelToken 导出了。
在vue-router路由守卫中引入并使用:
import { cancelToken } from "@/utils/request";
//路由进入前
router.beforeEach((to, from, next) => {
//取消上个路由的请求
cancelToken.forEach(item => {
item.cancel();
});
next();
});
目前我们是取消了所有在cancelToken 中的请求。
现在我们要做的是,如何不取消全局性的请求?
如何不取消全局性的请求?
最根本的办法就是加入判断条件, 我们可以在push到cancelToken数组的对象中,添加一个属性,表示这个请求是全局的请求。
//记录本次请求
cancelToken.push({
url: requestUrl,
cancel: source.cancel,
routeChangeCancel: true
});
默认情况我们可以将routeChangeCancel设为true,表示这个请求可以在路由切换时取消。
那么我们怎么配置这个属性呢?
可以通过axios的defaults默认配置添加一个routeChangeCancel
axios.create({//我就是defaults});
眼熟吧!
当然这个对象还能是这样传入
import axios from "axios";
//创建自定义axios示例
let api = axios.create({});
api.get("xxxx",{//我就是defaults});
import axios from "axios";
//创建自定义axios示例
let api = axios.create({});
api.defaults.routeChangeCancel = true;
很多种方式可以设置。
获取这个值得时候可以在拦截器中的config获取。
config.routeChangeCancel
于是乎改造:
import axios from "axios";
//创建自定义axios示例
let api = axios.create({
routeChangeCancel: true, //默认所有请求都可以在路由页切换时取消
});
//取消请求的数组
let cancelToken = [];
//请求前拦截
api.interceptors.request.use(config => {
//获取请求协议+路径
const requestUrl = `${config.method} ${config.url}`;
//查找取消请求标识中是否存在相同的requestUrl
const cancelIndex = cancelToken.findIndex(item => {
return item.url === requestUrl;
});
//存在就说明重复了
if (cancelIndex > -1) {
//取消
cancelToken[cancelIndex].cancel();
//删除该标识
cancelToken.splice(cancelIndex, 1);
};
//添加响应拦截中成功后删除该标识的判断条件
config.requestUrl = requestUrl;
//传入本地取消请求
cancelToken.push({
url: requestUrl,
cancel: source.cancel,
routeChangeCancel: config.routeChangeCancel
});
return config;
}, error => Promise.reject(error));
//响应拦截器删除取消标识中已完成的请求
const responseDeleteCancelToken = config => {
const cancelIndex = cancelToken.findIndex(item => {
return item.url === config.requestUrl
});
if (cancelIndex > -1) {
cancelToken.splice(cancelIndex, 1);
};
};
//axios响应拦截器
api.interceptors.response.use(response => {
//响应拦截器删除取消标识中已完成的请求
responseDeleteCancelToken(response.config)
//其他自定义的操作
....
}, error => {
//响应拦截器删除取消标识中已完成的请求
const response = error.response;
// 请求已发出,但服务器响应的状态码不在 2xx 范围内
if (response) {
//删除取消标识
responseDeleteCancelToken(response.config);
}
//取消的请求给错误对象添加一个标识
if (axios.isCancel(error)) {
error.selfCancel = true;
}
return Promise.reject(error);
});
export { api, cancelToken };
设置好标识后,我们在路由守卫里加上判断:
import { cancelToken } from "@/utils/request";
//路由进入前
router.beforeEach((to, from, next) => {
//取消上个路由的请求
cancelToken.forEach(item => {
item.routeChangeCancel && item.cancel();
});
next();
});
于是乎,一切都完美了。
目前来说这种方式满足了很多情况下的取消请求,感谢那篇文章的作者,让我对请求的取消方式,有了新的理解。
补充冷知识
你以为你取消了请求,后端就接不到了?
nonono,大错特错
虽然axios的取消请求确实用了xhr的abort()
取消,但是这个取消是看时机的,如果请求已经发出了才取消,那么后端还是可以接收到你的请求,如果在发出前就取消了,那么后端就收不到了。
那么后端一般是怎么处理这种的呢?
如果检测到你的请求被取消了,那么后端会取消发送。
没错,是取消发送,不是停止查询,所以实际上我们取消请求后,后端接受到了,还是会触发它的代码运行,只不过在返回结果的时候会判断,如果连接断了,就不在发送结果了。
以上就是关于vue中axios取消请的知识。
本文系作者 @木灵鱼儿 原创发布在木灵鱼儿站点。未经许可,禁止转载。
暂无评论数据