axios请求拦截:添加额外参数:
平时接口封装:
import request from '@/utils/request';
//
export const getList = async (data, otherConfig) => {
return await request({
url: `getList`,
method: 'post',
data,
otherConfig
});
};
// 平时使用,特殊页面
const res = await getList(data,{loading:true,...})
场景1:如果确定该接口为长接口,比较耗时等,其他相关配置baseURL等
export const getList = async (data, otherConfig) => {
return await request({
url: `getList`,
method: 'post',
data,
otherConfig:{
...otherConfig,
timeout:60000,
baseURL:'/api'
}
});
};
// response 拦截器
service.interceptors.response.use(
(response) => {
const res = response.data;
// 其他处理省略...
return res;
},
async (error) => {
// 根据实际情况设置
// 如果是返回reject则会被catch捕获,await之后的代码不会执行
// return Promise.reject(error);
// 如果是以下,则不会被catch捕获,await后代码会执行
return Promise.resolve(error)
}
);
普通情况:接口error错误时返回 Promise.reject(err) 不再推荐
接口响应拦截处理
错误时返回 return Promise.reject(error) === 中断拦截
const funA = async () => {
try{
const res = await 异常接口()
console.log('AAA:',res);
}catch(err){
console.log('AAA:err',err);
}
}
const funB = async () => {
try{
const res = await 正常接口()
console.log('BBB:',res);
}catch(err){
console.log('BBB:err',err);
}
}
需求一:需要等A执行完再执行B
// 生命周期调用
onMounted(async () => {
await funA();
await funB();
});
场景1:代码习惯使用 try{} catch(err){} 捕获
// 执行结果
!被catch捕获,打印 AAA:err 跟 BBB
场景2:代码习惯不使用 try{} catch(err){} 捕获
// 执行结果
直接报错,什么都不打印,==>>> funB会被funA拦截,也就是说A接口异常都会影响其他的
场景3:funA、funB 都正常,但是A接口比较慢,使用 try catch 捕获
// 执行结果
打印顺序正常
场景4:funA、funB 都正常,但是A接口比较慢,使用 try catch 捕获,生命周期不使用 await
onMounted(async () => {
funA();
funB();
});
// 执行结果
先打印BBB,再打印AAA,无法做到异步效果
综上所述:
想要防止一个接口异常影响其他接口,需要每个接口都要用 try catch 去捕获异常并写逻辑,或者使用 await 接口().catch(err=>{ ... })
需要做到异步效果,也是等一个接口处理完再去处理其他逻辑,则需要在生命周期使用 async await
特殊情况:错误时返回 Promise.resolve(err) 推荐写法
接口响应拦截处理
错误时返回 return Promise.resolve(error) === 放行
const funA = async () => {
try{
const res = await 异常接口()
console.log('AAA:',res);
}catch(err){
console.log('AAA:err',err);
}
}
const funB = async () => {
try{
const res = await 正常接口()
console.log('BBB:',res);
}catch(err){
console.log('BBB:err',err);
}
}
需求一:需要等A执行完再执行B
// 生命周期调用
onMounted(async () => {
await funA();
await funB();
});
场景1:代码习惯使用 try{} catch(err){} 捕获
// 执行结果
打印 AAA 跟 BBB ===>>> 无法被 catch 捕获! 无论正常异常都会进入 try
场景2:代码习惯不使用 try{} catch(err){} 捕获
// 执行结果
打印 AAA 跟 BBB ===>>> 打印顺序正常!
场景3:funA、funB 都正常, 生命周期使用 await ,但是A接口比较慢,
// 执行结果
先后打印顺序正常,异步正常
场景4:funA、funB 都正常,但是A接口比较慢,生命周期不使用 await
onMounted(async () => {
funA();
funB();
});
// 执行结果
先打印BBB,再打印AAA,无法做到异步效果
综上所述,如果想要捕获接口异常并做出处理,只能通过 res.code === 0/200 (根据后端状态码) 去进行判断,或者只做成功判断(日常接口使用)
要达到 异步效果则需要在生命周期/方法 使用 async await
最终接口axios封装文件
import axios from 'axios';
import { getToken } from '@/utils/token';
import { ElNotification, ElMessageBox, ElMessage, ElLoading } from 'element-plus';
import router from '@/router';
import { useUserStore } from '@/stores/user';
/**
* @description: request作用域内的全局变量和函数
* @param TIMEOUT 请求超时时间 ms
* @param TimeoutMsg 请求超时时间 提示
* @param reqWithLoadingNum 开启loading的请求数
* @param duration 保证toast显示3s 才被关闭 避免提示过短
* @param timeoutTimer 超时提醒定时器
* @param loadingInstance loading实例
*/
const TIMEOUT = 30000;
const TimeoutMsg = '网络异常,请求超时';
let reqWithLoadingNum = 0;
let duration = 3000;
let timeoutTimer = null;
let loadingInstance = null;
/**
* @description: 开启loading
*/
const showLoading = (params) => {
// 同一个页面仅第一次loading展示 避免页面多次出现loading
if (reqWithLoadingNum <= 0) {
// showloading
loadingInstance = ElLoading.service({
fullscreen: true,
lock: true,
text: params || '接口请求中~',
background: 'rgba(0, 0, 0, 0.7)'
});
// 做超时处理
timeoutTimer && clearTimeout(timeoutTimer);
timeoutTimer = setTimeout(() => {
loadingInstance.close();
clearTimeout(timeoutTimer);
if (reqWithLoadingNum > 0) {
// 存在请求超时
ElMessage({
type: 'error',
message: TimeoutMsg,
duration
});
}
}, TIMEOUT + 1000);
}
reqWithLoadingNum += 1;
};
/**
* @description: 关闭loading
*/
const hideLoading = (force = false) => {
reqWithLoadingNum -= 1;
if (reqWithLoadingNum <= 0 || force) {
reqWithLoadingNum = 0;
loadingInstance.close();
}
};
const service = axios.create({
// api 的 base_url
baseURL: import.meta.env.VITE_APP_BASE_API,
timeout: 30000
});
/**
* @description: request拦截器
* @param extendParam <Object> 自定义config,自定义timeout、loading
* 特殊接口timeout需要加长
*/
service.interceptors.request.use(
(config) => {
if (config?.otherConfig) {
config = {
...config,
...config.otherConfig
};
}
if (getToken()) {
// 让每个请求携带自定义token 请根据实际情况自行修改
config.headers['x-auth-token'] = getToken();
}
// 全局AXIOS请求加载
if (config?.otherConfig?.loading === true) {
showLoading();
}
return config;
},
(err) => {
Promise.reject(err);
}
);
// response 拦截器
service.interceptors.response.use(
(response) => {
if (response?.config?.otherConfig?.loading === true) {
hideLoading();
}
const res = response.data;
if (res.code) {
// 不能在头部引入,调用顺序问题,pinia还未被创建
const userStore = useUserStore();
switch (res.code) {
case 70403:
case 71402:
case 401:
ElMessageBox.alert('登录状态已过期,请您重新登录!', '系统提示', {
confirmButtonText: '重新登录',
type: 'warning'
}).then(() => {
userStore.logout().then(() => {
router.push('/login');
});
});
break;
case 70401:
case 403:
ElNotification.error({
title: '错误',
message: res.message || '您无权进行此操作,请求执行已拒绝'
});
break;
case 0:
case 200:
break;
case 201:
case 202:
case 203:
ElNotification.success({
title: '成功',
message: res.message
});
break;
default:
ElNotification.error({
title: '错误',
message: res.message || '请求失败'
});
break;
}
}
return res;
},
async (err) => {
if (err?.config?.otherConfig?.loading === true) {
hideLoading();
}
ElNotification.error({
title: '错误',
message: err?.message || '请求失败'
});
// 根据实际情况设置
// 如果是返回reject则会被catch捕获,await之后的代码不会执行
// return Promise.reject(error);
// 如果是以下两种,则不会被catch捕获,await后代码会执行
return Promise.resolve(err);
// return error
}
);
export default service;
后续会不断更新