拦截器配置
使用场景:
- 路由拦截
导航守卫 + requiresAuth + 响应拦截器
router.beforeEach((to, from) => {
if (to.meta.requiresAuth && !token) {
// 此路由需要授权,请检查是否已登录
// 如果没有,则重定向到登录页面
return {
path: '/login',
// 保存我们所在的位置,以便以后再来
query: { redirect: to.fullPath },
}
}
});
// 使用响应拦截器处理本地token失效情况,强制用户重新登录
axios.interceptors.response.use(
() => {},
(error) => {
if (error.response.status) {
switch (error.response.status) {
// token失效
case 401:
// 清空token, 跳转登录页
break;
....
....
}
}
})
- 统一处理http请求与响应
- 登录失效处理
- 请求头添加token
- 对响应错误分类处理
- showLoading加载动画处理
- 对响应数据的嵌套层级过深的处理
// 定义拦截器,实现拦截功能单一化
import request from './request/index'; // 定义请求拦截器
import response from './response/index'; // 定义响应拦截器
// 定义拦截器调度器,注册所有拦截器
export const setInterceptors = instance => {
if(!instance) return;
// 设置请求拦截器
for (const key in request) {
instance.interceptors.request.use((config) => request[key](config));
}
// 设置响应拦截器
for (const key in response) {
instance.interceptors.response.use((response) => response[key](response));
}
return instance;
}
// 响应错误分类处理
axios.interceptors.response.use(
(response) => {},
(error) => {
if (error.response.status) {
switch (error.response.status) {
// token失效
case 401:
// 清空token, 跳转登录页
break;
// 404请求不存在
case 404:
...
break;
// SQL语句错误
case 500:
...
break;
// 针对其他错误的处理
....
....
}
}
return Promise.reject(error.response);
},
);
- 请求取消, 解决并发冲突
使用api: CancelToken, AbortController(v0.22.0+); 并发冲突:不同用户在较短时间间隔内进行相同的操作,或者某一个用户进行的重复提交操作
请求取消:
let cancel;
axios.get('/user/12345', {
cancelToken: new axios.CancelToken(function executor(c) {
// executor 函数接收一个 cancel 函数作为参数
cancel = c;
})
});
// 取消请求
cancel();
// AbortController实现
// const controller = new AbortController();
let source = axios.CancelToken.source();
axios.get('/user/12345', {
cancelToken: source.token,
// signal: controller.signal
}).catch(function (thrown) {
if (axios.isCancel(thrown)) {
console.log('Request canceled', thrown.message);
} else {
// 处理错误
}
});
source.cancel('取消请求');
// 恢复请求
source = axios.CancelToken.source();
// controller.abort();
并发解决:
- 一般通用的解决方案
给页面控件添加Loading效果,避免用户重复点击操作
- 使用拦截器 + 路由导航守卫解决并发冲突:
// 1. 缓存所有进行中的请求(使用url + 方法名作为请求的标识),并在此次请求发送前进行判断,有则取消
// 2. 请求完成在缓存的请求列表中清除该请求
// 3. 如有需要,响应拦截器中设置相同的请求再次触发时需要满足的延迟条件(避免短时间内大量重复发送)
// 4. 某些特定情况下(网络问题、超时、路由切换)清空缓存的请求
this.pendings = new Map();
axios.interceptors.request.use(
(config) => {
// 缓存请求,并对当前请求进行判断
this.pendings.set(url, cancel);
},
(error) => {
// 网络波动或者超时等情况,清空 pendingRequests 对象
pendingRequests.clear();
return Promise.reject(error);
}
);
axios.interceptors.response.use(
(response) => {
// 从缓存列表中清除该请求
// 如有必要,设置再次触发请求的延迟
setTimeout(() => {
this.pendings.remove();
}, 1000);
},
(error) => {
if (axios.isCancel(error)) {
return Promise.reject(error);
}
pendingRequests.clear();
return Promise.reject(error);
}
)
router.beforeEach((to, from, next) => {
// 路由跳转要清除之前所有的请求缓存
this.pendings.clear();
next();
})
请求参数配置
params序列化
get请求参数类型为数组时,queryString被编码为如下格式
{
types: [1,2],
keyword: 'aaa bbb'
}
types[]=1&types[]=2&keyword=aaa+bbb // 后端无法解析types
解决方法:
1. 使用URLSearchParams对象
const params = new URLSearchParams();
params.append('types', [1,2]);
params.append('keyword', 'aaa bbb');
// 编码为
types=1%2C3&keyword=aaa+bbb
2. 使用paramsSerializer配置选项
(1) qs库
paramsSerializer: (params) => Qs.stringify(params, { indices: false })
// types=1&types=2&keyword=aaa%20bbb
(2) encodeURI/Array.join()
针对值为数组的参数,使用encodeURI/Array.join()
// types=1,2&keyword=aaa+bbb
补充: qs与queryString的区别:querystring无法解析嵌套对象
后语
实际项目中axios的一些封装使用:
拦截器调度器配置(单一化拦截器功能)、全局错误处理、特殊场景处理(如取消重复请求、请求异常处理、路由拦截等)
未完待续~~~~~~
参考链接: 77.9K Star 的 Axios 项目有哪些值得借鉴的地方、 优雅管理axios拦截器、 qs、 js-URI编码、解码