问题背景
在新项目开始时,axios的封装是必须的,这里就总结回顾一下axios都需要进行哪些封装把
基础配置(参考vue-element-admin,可直接使用)
1.请求自动携带token
2.统一处理错误情况
3.默认去除response的包装,只返回data。通过meta的responseAll配置为true获取所有的response
其中请求拦截器的逻辑为:如果用户登陆了有token,则在请求头上携带token
其中响应拦截器的逻辑为:
返回的code是否为200
是:根据配置返回全部的res或者直接返回data
否:message提示用户,抛出异常
并且同时判断是否为token有问题的情况
有问题:让用户确认是否退出
确认退出:调用vuex退出系统的方法并重新加载一下login页面
import axios from'axios'import{ Message }from'element-ui'import store from'@/store'import{ getToken }from'@/utils/auth'// 创建axios实例const service = axios.create({baseURL: process.env.BASE_API,// api的base_urltimeout:5000,// 请求超时时间})// request拦截器
service.interceptors.request.use(config=>{// 如果登录了,有token,则请求携带token// Do something before request is sentif(store.state.userInfo.token){
config.headers['X-Token']=getToken()// 让每个请求携带token--['X-Token']为自定义key 请根据实际情况自行修改}return config
},error=>{// Do something with request error
console.log(error)// for debug
Promise.reject(error)})// respone拦截器
service.interceptors.response.use(// response => response,/**
* 下面的注释为通过response自定义code来标示请求状态,当code返回如下情况为权限有问题,登出并返回到登录页
* 如通过xmlhttprequest 状态码标识 逻辑可写在下面error中
*/response=>{const res = response.data
// 处理异常的情况if(res.code !==200){Message({message: res.message,type:'error',duration:5*1000,})// 403:非法的token; 50012:其他客户端登录了; 401:Token 过期了;if(res.code ===403|| res.code ===50012|| res.code ===401){
MessageBox.confirm('你已被登出,可以取消继续留在该页面,或者重新登录','确定登出',{confirmButtonText:'重新登录',cancelButtonText:'取消',type:'warning',}).then(()=>{
store.dispatch('FedLogOut').then(()=>{
location.reload()// 为了重新实例化vue-router对象 避免bug})})}return Promise.reject('error')}else{// 默认只返回data,不返回状态码和message// 通过 meta 中的 responseAll 配置来取决后台是否返回所有数据(包括状态码,message和data)const isbackAll = response.config.meta && response.config.meta.responseAll
if(isbackAll){return res
}else{return res.data
}}},error=>{
console.log('err'+ error)// for debugMessage({message: error.message,type:'error',duration:5*1000,})return Promise.reject(error)})exportdefault service
如果不想配置过多的axios,那么上面的代码已经可以满足需求了。搭配使用方式为:
优化一:取消重复请求
参考地址:https://juejin.cn/post/6968630178163458084#heading-7
发生重复请求的场景一般有这两个(主要还是tab的切换会导致数据错乱):
- 对于列表数据,可能有tab状态栏的频繁切换查询,如果请求响应很慢,也会产生重复请求。当然现在很多列表都会做缓存,如Vue中用
- 快速连续点击一个按钮,如果这个按钮未进行控制,就会发出重复请求,假设该请求是生成订单,那么就有产生两张订单了,这是件可怕的事情。当然一般前端会对这个按钮进行状态处理控制,后端也会有一些幂等控制处理策略啥的,这是个假设场景,但也可能会发生的场景。
实现思路:
我们大致整体思路就是收集正在请求中的接口,也就是接口状态还是pending状态的,让他们形成队列储存起来。如果相同接口再次被触发,则直接取消正在请求中的接口并从队列中删除,再重新发起请求并储存进队列中;如果接口返回结果,就从队列中删除,以此过程来操作。
效果:同时发送三个请求,出现这个canceled就是成功的把前两次请求给取消了
需要用到的几个函数
// axios.jsconst pendingMap =newMap();/**
* 生成每个请求唯一的键
* @param {*} config
* @returns string
*/functiongetPendingKey(config){let{url, method, params, data}= config;if(typeof data ==='string') data =JSON.parse(data);// response里面返回的config.data是个字符串对象return[url, method,JSON.stringify(params),JSON.stringify(data)].join('&');}/**
* 储存每个请求唯一值, 也就是cancel()方法, 用于取消请求
* @param {*} config
*/functionaddPending(config){const pendingKey =getPendingKey(config);
config.cancelToken = config.cancelToken ||newaxios.CancelToken((cancel)=>{if(!pendingMap.has(pendingKey)){
pendingMap.set(pendingKey, cancel);}});}/**
* 删除重复的请求
* @param {*} config
*/functionremovePending(config){const pendingKey =getPendingKey(config);if(pendingMap.has(pendingKey)){const cancelToken = pendingMap.get(pendingKey);cancelToken(pendingKey);
pendingMap.delete(pendingKey);}}
axios简化版代码(方便看上面方法加入的位置):
// axios.jsconst service = axios.create({baseURL:'http://localhost:8888',// 设置统一的请求前缀timeout:10000,// 设置统一的超时时长});
service.interceptors.request.use(config=>{// 删除重复的请求removePending(config);// 如果repeatRequest不配置,那么该请求则不能多次请求!config.repeatRequest &&addPending(config)return config;},error=>{return Promise.reject(error);});
service.interceptors.response.use(response=>{// 删除重复的请求removePending(response.config);return response;},error=>{// 删除重复的请求
error.config &&removePending(error.config);return Promise.reject(error);});
加入到基础配置后的代码:
import axios from'axios'import{ Message }from'element-ui'import store from'@/store'import{ getToken }from'@/utils/auth'// 创建axios实例const service = axios.create({baseURL: process.env.BASE_API,// api的base_urltimeout:5000,// 请求超时时间})// request拦截器
service.interceptors.request.use(config=>{removePending(config)// 如果repeatRequest不配置,那么默认该请求就取消重复接口请求!config.repeatRequest &&addPending(config)// 如果登录了,有token,则请求携带token// Do something before request is sentif(store.state.userInfo.token){
config.headers['X-Token']=getToken()// 让每个请求携带token--['X-Token']为自定义key 请根据实际情况自行修改}return config
},error=>{// Do something with request error
console.log(error)// for debug
Promise.reject(error)})// respone拦截器
service.interceptors.response.use(// response => response,/**
* 下面的注释为通过response自定义code来标示请求状态,当code返回如下情况为权限有问题,登出并返回到登录页
* 如通过xmlhttprequest 状态码标识 逻辑可写在下面error中
*/response=>{// 已完成请求的删除请求中数组removePending(response.config);const res = response.data
// 处理异常的情况if(res.code !==200){Message({message: res.message,type:'error',duration:5*1000,})// 403:非法的token; 50012:其他客户端登录了; 401:Token 过期了;if(res.code ===403|| res.code ===50012|| res.code ===401){
MessageBox.confirm('你已被登出,可以取消继续留在该页面,或者重新登录','确定登出',{confirmButtonText:'重新登录',cancelButtonText:'取消',type:'warning',}).then(()=>{
store.dispatch('FedLogOut').then(()=>{
location.reload()// 为了重新实例化vue-router对象 避免bug})})}return Promise.reject('error')}else{// 默认只返回data,不返回状态码和message// 通过 meta 中的 responseAll 配置来取决后台是否返回所有数据(包括状态码,message和data)const isbackAll = response.config.meta && response.config.meta.responseAll
if(isbackAll){return res
}else{return res.data
}}},error=>{
error.config &&removePending(error.config)
console.log('err'+ error)// for debugMessage({message: error.message,type:'error',duration:5*1000,})return Promise.reject(error)})// axios.jsconst pendingMap =newMap();/**
* 生成每个请求唯一的键
* @param {*} config
* @returns string
*/functiongetPendingKey(config){let{url, method, params, data}= config;if(typeof data ==='string') data =JSON.parse(data);// response里面返回的config.data是个字符串对象return[url, method,JSON.stringify(params),JSON.stringify(data)].join('&');}/**
* 储存每个请求唯一值, 也就是cancel()方法, 用于取消请求
* @param {*} config
*/functionaddPending(config){const pendingKey =getPendingKey(config);
config.cancelToken = config.cancelToken ||newaxios.CancelToken((cancel)=>{if(!pendingMap.has(pendingKey)){
pendingMap.set(pendingKey, cancel);}});}/**
* 删除重复的请求
* @param {*} config
*/functionremovePending(config){const pendingKey =getPendingKey(config);if(pendingMap.has(pendingKey)){const cancelToken = pendingMap.get(pendingKey);cancelToken(pendingKey);
pendingMap.delete(pendingKey);}}exportdefault service
搭配使用api使用,比如某个请求是可以同时进行请求的:
提问:取消了接口的重复请求,还有必要做按钮的防抖节流嘛?
回:不一定,取消重复请求只是在前端过滤了这个请求,后端还是收到了2份请求的,如果是表单类的提交,那数据库还是会生成2条数据的
优化二:配置loading
参考:https://blog.csdn.net/weixin_43239880/article/details/121688263?spm=1001.2014.3001.5501
为什么需要在axios中配置loading?
原因:每次发请求都需要去配置一个值,然后请求开始将该值设为true,请求完毕设为false。感觉很麻烦
效果:发送请求时加入配置:可以让任意一个盒子loading
const res3 =awaitgetListById({loading:true,loadingDom:".bg3"})
需要用到的函数
const LoadingInstance ={_target:null,// 保存Loading实例_count:0,// 计算数量,保证一次只有一个loading}functionopenLoading(loadingDom){
LoadingInstance._target = Loading.service({lock:true,text:'数据正在加载中',spinner:'el-icon-loading',background:'rgba(25, 32, 53, 1)',target: loadingDom ||'body',})}functioncloseLoading(){if(LoadingInstance._count >0) LoadingInstance._count--if(LoadingInstance._count ===0){
LoadingInstance._target.close()
LoadingInstance._target =null}}
axios简化版代码(方便看上面方法加入的位置):
// axios.jsconst service = axios.create({baseURL:'http://localhost:8888',// 设置统一的请求前缀timeout:10000,// 设置统一的超时时长});
service.interceptors.request.use(config=>{// 打开loadingif(config.loading){
LoadingInstance._count++if(LoadingInstance._count ===1){openLoading(config.loadingDom)}}return config;},error=>{return Promise.reject(error);});
service.interceptors.response.use(response=>{// 关闭loadingif(response.config.loading){closeLoading()}return response;},error=>{// 关闭loadingif(error.config.loading){closeLoading()}return Promise.reject(error);});
加入到基础配置后的代码。这也是最终的完整代码:
import axios from'axios'import{ Message, Loading }from'element-ui'import store from'@/store'import{ getToken }from'@/utils/auth'// 创建axios实例const service = axios.create({baseURL: process.env.BASE_API,// api的base_urltimeout:5000,// 请求超时时间})// request拦截器
service.interceptors.request.use(config=>{removePending(config)// 如果repeatRequest不配置,那么默认该请求就取消重复接口请求!config.repeatRequest &&addPending(config)// 打开loadingif(config.loading){
LoadingInstance._count++if(LoadingInstance._count ===1){openLoading(config.loadingDom)}}// 如果登录了,有token,则请求携带token// Do something before request is sentif(store.state.userInfo.token){
config.headers['X-Token']=getToken()// 让每个请求携带token--['X-Token']为自定义key 请根据实际情况自行修改}return config
},error=>{// Do something with request error
console.log(error)// for debug
Promise.reject(error)})// respone拦截器
service.interceptors.response.use(// response => response,/**
* 下面的注释为通过response自定义code来标示请求状态,当code返回如下情况为权限有问题,登出并返回到登录页
* 如通过xmlhttprequest 状态码标识 逻辑可写在下面error中
*/response=>{// 已完成请求的删除请求中数组removePending(response.config)// 关闭loadingif(response.config.loading){closeLoading()}const res = response.data
// 处理异常的情况if(res.code !==200){Message({message: res.message,type:'error',duration:5*1000,})// 403:非法的token; 50012:其他客户端登录了; 401:Token 过期了;if(res.code ===403|| res.code ===50012|| res.code ===401){
MessageBox.confirm('你已被登出,可以取消继续留在该页面,或者重新登录','确定登出',{confirmButtonText:'重新登录',cancelButtonText:'取消',type:'warning',}).then(()=>{
store.dispatch('FedLogOut').then(()=>{
location.reload()// 为了重新实例化vue-router对象 避免bug})})}return Promise.reject('error')}else{// 默认只返回data,不返回状态码和message// 通过 meta 中的 responseAll 配置来取决后台是否返回所有数据(包括状态码,message和data)const isbackAll = response.config.meta && response.config.meta.responseAll
if(isbackAll){return res
}else{return res.data
}}},error=>{
error.config &&removePending(error.config)// 关闭loadingif(error.config.loading){closeLoading()}
console.log('err'+ error)// for debugMessage({message: error.message,type:'error',duration:5*1000,})return Promise.reject(error)})// --------------------------------取消接口重复请求的函数-----------------------------------// axios.jsconst pendingMap =newMap()/**
* 生成每个请求唯一的键
* @param {*} config
* @returns string
*/functiongetPendingKey(config){let{ url, method, params, data }= config
if(typeof data ==='string') data =JSON.parse(data)// response里面返回的config.data是个字符串对象return[url, method,JSON.stringify(params),JSON.stringify(data)].join('&')}/**
* 储存每个请求唯一值, 也就是cancel()方法, 用于取消请求
* @param {*} config
*/functionaddPending(config){const pendingKey =getPendingKey(config)
config.cancelToken =
config.cancelToken ||newaxios.CancelToken(cancel=>{if(!pendingMap.has(pendingKey)){
pendingMap.set(pendingKey, cancel)}})}/**
* 删除重复的请求
* @param {*} config
*/functionremovePending(config){const pendingKey =getPendingKey(config)if(pendingMap.has(pendingKey)){const cancelToken = pendingMap.get(pendingKey)cancelToken(pendingKey)
pendingMap.delete(pendingKey)}}// ----------------------------------loading的函数-------------------------------const LoadingInstance ={_target:null,// 保存Loading实例_count:0,}functionopenLoading(loadingDom){
LoadingInstance._target = Loading.service({lock:true,text:'数据正在加载中',spinner:'el-icon-loading',background:'rgba(25, 32, 53, 1)',target: loadingDom ||'body',})}functioncloseLoading(){if(LoadingInstance._count >0) LoadingInstance._count--if(LoadingInstance._count ===0){
LoadingInstance._target.close()
LoadingInstance._target =null}}exportdefault service
loading搭配api使用:
loading页面中使用:
优化三:用qs模块来序列化参数
我这边没有使用qs进行序列化。主要原因是因为现在前后端交互的数据格式主流就是json格式。只有图片上传接口会使用表单的方式进行提交。 如果是个别的post接口,后台要求你用表单提交,你可以进行交涉下。
前后端交互格式参考https://blog.csdn.net/qq_43654065/article/details/114642300
最终的完整代码
import axios from'axios'import{ Message, Loading }from'element-ui'import store from'@/store'import{ getToken }from'@/utils/auth'// 创建axios实例const service = axios.create({baseURL: process.env.BASE_API,// api的base_urltimeout:5000,// 请求超时时间})// request拦截器
service.interceptors.request.use(config=>{removePending(config)// 如果repeatRequest不配置,那么默认该请求就取消重复接口请求!config.repeatRequest &&addPending(config)// 打开loadingif(config.loading){
LoadingInstance._count++if(LoadingInstance._count ===1){openLoading(config.loadingDom)}}// 如果登录了,有token,则请求携带token// Do something before request is sentif(store.state.userInfo.token){
config.headers['X-Token']=getToken()// 让每个请求携带token--['X-Token']为自定义key 请根据实际情况自行修改}return config
},error=>{// Do something with request error
console.log(error)// for debug
Promise.reject(error)})// respone拦截器
service.interceptors.response.use(// response => response,/**
* 下面的注释为通过response自定义code来标示请求状态,当code返回如下情况为权限有问题,登出并返回到登录页
* 如通过xmlhttprequest 状态码标识 逻辑可写在下面error中
*/response=>{// 已完成请求的删除请求中数组removePending(response.config)// 关闭loadingif(response.config.loading){closeLoading()}const res = response.data
// 处理异常的情况if(res.code !==200){Message({message: res.message,type:'error',duration:5*1000,})// 403:非法的token; 50012:其他客户端登录了; 401:Token 过期了;if(res.code ===403|| res.code ===50012|| res.code ===401){
MessageBox.confirm('你已被登出,可以取消继续留在该页面,或者重新登录','确定登出',{confirmButtonText:'重新登录',cancelButtonText:'取消',type:'warning',}).then(()=>{
store.dispatch('FedLogOut').then(()=>{
location.reload()// 为了重新实例化vue-router对象 避免bug})})}return Promise.reject('error')}else{// 默认只返回data,不返回状态码和message// 通过 meta 中的 responseAll 配置来取决后台是否返回所有数据(包括状态码,message和data)const isbackAll = response.config.meta && response.config.meta.responseAll
if(isbackAll){return res
}else{return res.data
}}},error=>{
error.config &&removePending(error.config)// 关闭loadingif(error.config.loading){closeLoading()}
console.log('err'+ error)// for debugMessage({message: error.message,type:'error',duration:5*1000,})return Promise.reject(error)})// --------------------------------取消接口重复请求的函数-----------------------------------// axios.jsconst pendingMap =newMap()/**
* 生成每个请求唯一的键
* @param {*} config
* @returns string
*/functiongetPendingKey(config){let{ url, method, params, data }= config
if(typeof data ==='string') data =JSON.parse(data)// response里面返回的config.data是个字符串对象return[url, method,JSON.stringify(params),JSON.stringify(data)].join('&')}/**
* 储存每个请求唯一值, 也就是cancel()方法, 用于取消请求
* @param {*} config
*/functionaddPending(config){const pendingKey =getPendingKey(config)
config.cancelToken =
config.cancelToken ||newaxios.CancelToken(cancel=>{if(!pendingMap.has(pendingKey)){
pendingMap.set(pendingKey, cancel)}})}/**
* 删除重复的请求
* @param {*} config
*/functionremovePending(config){const pendingKey =getPendingKey(config)if(pendingMap.has(pendingKey)){const cancelToken = pendingMap.get(pendingKey)cancelToken(pendingKey)
pendingMap.delete(pendingKey)}}// ----------------------------------loading的函数-------------------------------const LoadingInstance ={_target:null,// 保存Loading实例_count:0,}functionopenLoading(loadingDom){
LoadingInstance._target = Loading.service({lock:true,text:'数据正在加载中',spinner:'el-icon-loading',background:'rgba(25, 32, 53, 1)',target: loadingDom ||'body',})}functioncloseLoading(){if(LoadingInstance._count >0) LoadingInstance._count--if(LoadingInstance._count ===0){
LoadingInstance._target.close()
LoadingInstance._target =null}}exportdefault service
总结
基础的axios即可满足大部分项目的需求,配置loading和取消重复请求就见仁见智了,可加可不加。
项目demo源码地址:https://github.com/rui-rui-an/packageAxios
版权归原作者 吃炸鸡的前端 所有, 如有侵权,请联系我们删除。