木灵鱼儿
阅读:4062
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取消请的知识。
版权申明
本文系作者 @木灵鱼儿 原创发布在木灵鱼儿 - 有梦就能远航站点。未经许可,禁止转载。
相关推荐
封装uni.request api为类似axios的用法
前言uniapp的请求都是通过uni.request的api实现的,但是这个api和前端常用的axios库用法上有着太多的不同了,移植起来非常痛苦,所以萌生了自己造一个轮子的想法,由于本人技术菜鸡,只能浅浅的仿照一个了。实际上本人喜欢axios的config的方式调用请求,如下:axios({ method: 'post', url: '/user/12345', data: { firstName: 'Fred', lastName: 'Flintstone' } });所以大体上都是围绕这种用法展开工作的。首先说下使用上...
实现一个点击空白区域关闭显示的自定义指令
typescriptimport type { DirectiveBinding } from "vue"; export default { bind(el: HTMLElement, binding: DirectiveBinding<Function>) { //声明一个给document绑定的事件 function documentClick(event: Event) { const target = event.target as unknown as Node; if (el.contains(t...
使用vue2.7的一些踩坑事项
eslint校验的一些问题(暂时无解)在初始化项目时勾选了eslint校验之后,升级vue 2.7版本后,eslint-plugin-vue这个插件需要升级到9+版本,我目前使用的版本是:"eslint-plugin-vue": "^9.4.0"具体的一些可以参考官方提供的2.7升级指南:2.7日志官方居然把这个写在了变更日志里面,按道理最好官方的文档上也有一份说明的,但是目前没有。虽然eslint的依赖更新到新版后确实解决了一些项目启动报错的问题,但是有时候我们的SFC单文件组件开发的时候,template中的一些变量绑定还是会出现波浪线警告,这...
正确使用vue3的ts类型声明
前言使用了ts最头疼的是什么,除了类型声明应该没有第二家了,那么在vue3中如何正确的声明ts类型,代表着我们踏出了认识vue3的第一步,这非常重要,所以为此水个文章,分享给有需要的人。Volar 插件一开始我对于Volar并没有太大的需要,因为一直使用的Vetur,而且这个插件刚出来时并不完善,各种视频up讲的那个一键分屏功能其实也并不好用,虽然是个很有意思的东西,但是没有那种非要使用它的点,所以当时的我怀着这么一个疑问?为什么要用Volar ?现在我就通过两张图告诉你,它有多香!我们在template里面写代码,绑定变量最烦的是什么,就是我们写了个对象,但是忘了它的属性有哪些啊,使用...

关于给css自动添加浏览器前缀
前言为了兼容之前的旧浏览器版本,特别是安卓4.4这种低版本,transform是一定得增加浏览器前缀的,但是我在项目中遇到了设置无效的问题,极度蛋疼,下面是我的解决教程,当然没这个问题,看这篇文章,相信你对如果给项目增加浏览器前缀,会有很充分的认识。教程给css增加浏览器前缀,业界的做法就是使用postcss,目前webpack与vue cli他们需要安装的依赖略有不同:webpack:pnpm i postcss postcss-loader autoprefixer -D添加对应的rules{ test: /.s?css$/, use: [ ...省略...
vuex 动态注册和卸载模块
概述一般情况下,我们的vuex数据都是静态的,store在首次初始化后数据的格式就定好了,在日常使用中也确实应该这么做。但是,随着业务的发展,vuex可能会变得非常的大,或者在多页面打包的时候,每个页面都需要vuex,但是如果把每个页面的vuex都写在一起,你会发现,原来我a页面可能只需要30个vuex的数据监听,但是会多出来其他页面的数据,这显然不应该的。所以,我们需要一个能够动态加载模块的方法,每个页面动态加载自己的vuex数据使用。api了解vuex官方提供了几个api:registerModule动态注册模块apiunregisterModule卸载一个动态模块hasModule...
vue-i18n 的使用方式
安装vue2版需要安装8.x版本的,9.x的是vue3版本使用上大同小异。vue2安装:yarn add vue-i18n@8vue3安装:yarn add vue-i18n封装官方虽然支持很不错的用法,但是自定义处理是难免的。vue3文件目录结构├─ src │ ├─ language │ │ ├─ lang │ │ │ ├─ en.json │ │ │ └─ zh.json │ │ ├─ core │ │ │ ├─ i18n.ts │ │ │ ├─ customization.ts │ │ │ └─ language.ts │ │ ├─ i...
vue router 一个重定向页面的思路
当项目需要和其他项目进行沟通的时候,往往常见的做法就是,我在a网站点击一个按钮,在链接中携带query参数啥的,然后去访问b网站页面。此时可能会有两种情况:跳转的链接就是b网站的具体链接地址。跳转的链接是b网站的一个跳板,跳板会对参数做一些操作,然后进行重定向。两种做法都各有优势:第一种省事简单,但是如果以后b网站链接层级发生变化,那么就会导致链接不可能,到时候还需要进行修改,很痛苦。第二种的话,我们需要写一个跳板页面,但是通过一些参数要求,甚至自身加上一些逻辑处理,那么他的功能性会更强一些。那么,在vue中,做一个重定向页面,怎么做?思路思路1利用路由守卫,当链接上存在某个参数的时候,...
vue 过滤器 驼峰与短横线相互转换的方法
自己去注册吧,提供的是es6导出的方法//短横线转驼峰 export const dashToHump = function (value) { const textArr = value.split("-"); return textArr.map((item, index) => { if (index === 0) return item.toLowerCase(); return item.slice(0, 1).toUpperCase() + item.slice(1); }) .join("")...
使用vue.draggable拖拽组件遇到的一些问题
资源github:vue.draggable中文文档:vue.draggable中文文档api参考文档:sortablejsvue.draggable是基于sortablejs的vue封装,所有有些api官方并不会有过多的解释,可以去sortablejs查看下拖拽无法触发页面滚动当拖拽的内容大于页面宽高时,页面滚动就是一个必然的需求,但是vue.draggable默认情况并不能触发滚动。官方设置里有一个属性:scroll,如果为true时就能触发滚动,但是默认属性就是true;所以这个配置可以说是无效的。解决方案:cannot set scrollSensitivity19年的时候就有提...
