一起养成写作习惯!这是我参与「掘金日新计划 · 4 月更文挑战」的第8天,点击查看活动详情。
引言
相信对Vue熟悉的读者对Axios不会感到陌生了,但是单纯的axios并不能满足我们日常的使用,因此很多时候我们都需要对axios进行二次封装以满足项目需求。下面,我们就来聊聊 Vue 中 axios 的封装。
Vue项目中如何封装axios
axios文件封装在目录src/utils/https.ts
,对外暴露callApi
函数
判断HTTP状态码
一个良好展示接口实时状态的提示信息是非常重要的,一方面方便前端人员定位问题原因,在一些复杂特殊场景也可以给予用户提示引导。
此处封装errorCode
共通方法,来实现解耦化,方便后期维护与代码阅读。
errorCode.ts
: 用于定义返回错误code
export default {
'400': '请求头错误',
'401': '认证失败,无法访问系统资源, 请重新登录',
'403': '当前操作没有权限',
'404': '访问资源不存在',
'500': '服务器端出错,
'503': '服务不可用',
'default': '系统未知错误,请反馈给管理员'
}
http.ts
: 用于axios
封装
import axios from 'axios'
import errorCode from '@/utils/errorCode'
import { ElMessage, ElMessageBox } from 'element-plus'
axios.defaults.headers['Content-Type'] = 'application/json;charset=utf-8'
// 创建axios实例
const service = axios.create({
// axios中请求配置有baseURL选项,表示请求URL公共部分
baseURL: process.env.VUE_APP_BASE_API,
// 超时
timeout: 10000
})
// request拦截器
service.interceptors.request.use(config => {
if (getToken() && !isToken) {
config.headers['Authorization'] = 'Bearer ' + getToken() // 让每个请求携带自定义token 请根据实际情况自行修改
}
return config
}, error => {
console.log(error)
Promise.reject(error)
})
// 响应拦截器
service.interceptors.response.use(res => {
// 未设置状态码则默认成功状态
const code = res.data.code || 200;
// 获取错误信息
const msg = res.data.msg || errorCode[code] || errorCode['default']
// 二进制数据则直接返回
if(res.request.responseType === 'blob' || res.request.responseType === 'arraybuffer'){
return res.data
}
// 判断状态码
if(code === 200){
return res.data
}else if(code === 401){
//跳转首页逻辑
return Promise.reject(msg)
}else{
ElMessage({
message: msg,
type: 'error'
})
return Promise.reject(msg)
}
},
error => {
let { message } = error;
if (message == "Network Error") {
message = "后端接口连接异常";
}
else if (message.includes("timeout")) {
message = "系统接口请求超时";
}
else if (message.includes("Request failed with status code")) {
message = "系统接口" + message.substr(message.length - 3) + "异常";
}
ElMessage({
message: message,
type: 'error',
duration: 5 * 1000
})
return Promise.reject(error)
}
)
export default service
防止重复请求Loading
在项目中数据异步请求是一个非常常见的场景,一个良好的Loading效果能很好的加强用户体验,如果在发起了一个请求后立即就出现一个Loading层,那么用户也就无法再次点击而造成重复多次请求了。
在项目中最常见的使用方式便是发起请求之前开启Loading和请求响应之后关闭Loading以达到防止重复请求的目的,但是在实际开发中往往会存在以下现象:
- 发起请求之前开启了Loading,但是请求调用成功之后忘记关闭,导致页面一直处于Loading状态。
- 发起请求之前忘记开启Loading,导致可能重复点击,影响用户体验。 因此为了避免上述问题的发生,决定将Loading封装在Axios中,以达到目的。
那么如何封装Loading需要我们考虑如下事情:
- 同一时间内发起多个请求,我们只需要展示一个Loading层即可,不需要产生多个造成重复展示。
- 同一时间内发起多个请求展示的Loading层以最后一个请求响应而关闭销毁。
- 此功能依旧要进行可配置化处理。
此处以 ElementPlus 的Loading效果为例进行开发。
此处封装loading
共通方法,来实现解耦化,方便后期维护与代码阅读。
loading.ts
: 用于定义接口loading,
/**
* 全局loading效果:合并多次loading请求,避免重复请求
* 当调用一次showLoading,则次数+1;当次数为0时,则显示loading
* 当调用一次closeLoading,则次数-1; 当次数为0时,则结束loading
*/
import { ElLoading } from 'element-plus';
// 定义一个请求次数的变量,用来记录当前页面总共请求的次数
let loadingRequestCount = 0;
// 初始化loading
let loadingInstance;
// 编写一个显示loading的函数 并且记录请求次数 ++
export showLoading = (target) => {
if (loadingRequestCount === 0) {
loadingInstance = ElLoading.service({
lock: true,
text: '加载中……',
background: 'rgba(0, 0, 0, 0.7)'
});
}
loadingRequestCount++
}
// 编写一个隐藏loading的函数,并且记录请求次数 --
export closeLoading = () => {
if (loadingRequestCount <= 0) return
loadingRequestCount--
if (loadingRequestCount === 0) {
loadingInstance.close();
}
}
http.ts
: 用于axios
封装
import axios from 'axios'
import { showLoading, closeLoading } from "@/utils/loading";
axios.defaults.headers['Content-Type'] = 'application/json;charset=utf-8'
// 创建axios实例
const service = axios.create({
// axios中请求配置有baseURL选项,表示请求URL公共部分
baseURL: process.env.VUE_APP_BASE_API,
// 超时
timeout: 10000
})
//开启loading
showLoading()
// request拦截器
service.interceptors.request.use(config => {
if (getToken() && !isToken) {
config.headers['Authorization'] = 'Bearer ' + getToken() // 让每个请求携带自定义token 请根据实际情况自行修改
}
return config
}, error => {
console.log(error)
Promise.reject(error)
})
// 响应拦截器
service.interceptors.response.use(res => {
//关闭loading,此处采用延时处理是合并loading请求效果,避免多次请求loading关闭又开启
setTimeout(() => {
closeLoading()
}, 200)
},
error => {
setTimeout(() => {
closeLoading()
}, 200)
return Promise.reject(error)
}
)
export default service
请求失败自动重试
在实际项目中,由于网络是不可靠的,所以经常会有请求失败的场景。针对这种问题,通常的做法是增加重试机制,在请求失败后自动重新发起多次请求,直到达到所设次数,尽量保证请求的成功,从而提高服务的稳定性。
common.ts
: 公共函数
// 判断一个字符串是否为JSON字符串
export let isJsonStr = str => {
if (typeof str == 'string') {
try {
var obj = JSON.parse(str);
if (typeof obj == 'object' && obj) {
return true;
} else {
return false;
}
} catch (e) {
console.log('error:' + str + '!!!' + e);
return false;
}
}
};
againRequest.ts
: 用于定义重试请求
import { isJsonStr } from './common';
export function againRequest(err, axios) {
let config = err.config;
// config.retry 具体接口配置的重发次数
if (!config || !config.retry) return Promise.reject(err);
// 设置用于记录重试计数的变量 默认为0
config.__retryCount = config.__retryCount || 0;
// 判断是否超过了重试次数
if (config.__retryCount >= config.retry) {
return Promise.reject(err);
}
// 重试次数
config.__retryCount += 1;
// 延时处理
var backoff = new Promise(function(resolve) {
setTimeout(function() {
resolve();
}, config.retryDelay || 1000);
});
// 重新发起axios请求
return backoff.then(function() {
// 判断是否是JSON字符串
// TODO: 未确认config.data再重发时变为字符串的原因
if (config.data && isJsonStr(config.data)) {
config.data = JSON.parse(config.data);
}
return axios(config);
});
}
http.ts
: 用于axios
封装
import axios from 'axios'
import { againRequest } from '@/utils/requestAgainSend';
axios.defaults.headers['Content-Type'] = 'application/json;charset=utf-8'
// 创建axios实例
const service = axios.create({
// axios中请求配置有baseURL选项,表示请求URL公共部分
baseURL: process.env.VUE_APP_BASE_API,
// 超时
timeout: 10000
})
// request拦截器
service.interceptors.request.use(config => {
return config
}, error => {
Promise.reject(error)
})
// 响应拦截器
service.interceptors.response.use(res => {
return res.data
},
error => {
// 需要特殊处理请求被取消的情况
if (!Axios.isCancel(error)) {
// 请求重发
return againRequest(error, axios);
}
}
)
export default service
总结
axios
封装没有一个绝对的标准,具体封装结果需要根据项目需求适当变化,希望以上封装案例能够给予您一些帮助。
结语
本文为笔者个人学习笔记,如有谬误,还请告知,万分感谢!如果本文对你有所帮助,还请点个关注点个赞~,您的支持是笔者不断更新的动力。