axios 的封装
理解
最开始我们学习axios时,我们的代码也许是这样的:
import axios from 'axios'
export function request(config) {
const instance = axios.create({
baseURL: 'http://127.0.0.1:8888/api/private/v1/',
timeout: 5000
})
// 请求拦截
instance.interceptors.request.use(config => {
config.headers.Authorization = 'xxx'
return config;
}, err => {
console.log(err);
})
// 响应拦截
instance.interceptors.response.use(res => {
return res;
}, err => {
console.log(err);
})
// 发送网络请求
return instance(config);
}
上述代码非常简单,axios.create()
创建一个实例,注册请求、响应拦截函数。
随着我们不断学习、工作就会发现一个问题:那就是项目中可能会存在多个后台服务器,比如项目里A模块是自己公司的后端人员提供的服务接口,但是B模块比较复杂,自己公司没有能力或没打算去做,就会交给别的公司去做。这就意味着,我们前端在一个项目中,需要对接多个不同的后台服务器接口。
所以,上面的代码就不怎么适用了。我们需要对axios进行更灵活的封装。
因为出现了多个后台服务器,我们应该创建多个不同的实例,又因为出现了多个实例,就应该考虑这个请求、响应拦截应该怎么写?
本文的拦截思想是对每一次请求进行三次拦截:
- 创建的所有实例的所有请求都统一会执行的拦截器
- 创建的某一个实例的所有请求统一会执行的拦截器
- 针对某一个请求单独会执行的拦截器
换句话说,发送一次请求,其请求会拦截三次,响应也会拦截三次。这样就非常灵活了,比如可以单独为不同的实例设置不同baseURL,timeout,也可以针对不同后台服务接口返回的字段结构进行响应拦截处理。
下面直接看代码吧~
代码
新建一个单独的request.js文件,这里写了一个类Request。
import axios from 'axios'
import { ElLoading } from 'element-plus'
const DEFAULT_SHOW_LOADING = false
class Request {
constructor(config) {
this.instance = axios.create(config)
this.interceptors = config.interceptors ?? {}
this.showLoading = config.showLoading ?? DEFAULT_SHOW_LOADING
this.instanceShowLoading = this.showLoading
this.loadingInstance = null
this.addInterceptors()
}
/**
* 添加拦截器
*/
addInterceptors() {
// 所有实例的拦截器
this.instance.interceptors.request.use(
config => {
console.log(3);
// 请求中动画...
if (this.showLoading === true) {
this.loadingInstance = ElLoading.service({
lock: true,
text: '加载中...',
background: 'rgba(0,0,0,.5)'
})
}
return config
},
error => {
this.loadingInstance?.close()
console.log('所有实例请求拦截器Error:', error);
return Promise.reject(error)
}
)
this.instance.interceptors.response.use(
response => {
console.log(4);
this.loadingInstance?.close()
return response.data
},
error => {
this.loadingInstance?.close()
console.log('所有实例响应拦截器Error:', error);
return Promise.reject(error)
}
)
// 当前实例的拦截器
const { requestInterceptor, requestInterceptorCatch, responseInterceptor, responseInterceptorCatch } = this.interceptors
this.instance.interceptors.request.use(requestInterceptor, requestInterceptorCatch)
this.instance.interceptors.response.use(responseInterceptor, responseInterceptorCatch)
}
/**
* 请求方法
* 封装了针对当个请求的拦截器
*/
request(config) {
const {
showLoading,
interceptors: {
requestInterceptor,
responseInterceptor,
responseInterceptorCatch
} = {}
} = config
if (requestInterceptor) {
config = requestInterceptor(config)
}
// 添加或取消loading
if (typeof showLoading === 'boolean') {
this.showLoading = showLoading
}
return new Promise((resolve, reject) => {
this.instance.request(config)
.then(response => {
if (responseInterceptor) {
response = responseInterceptor(response)
}
// 恢复loading设置
this.resetShowLoading()
resolve(response)
})
.catch(error => {
if (responseInterceptorCatch) {
error = responseInterceptorCatch()
}
// 恢复loading设置
this.resetShowLoading()
reject(error)
})
})
}
/**
* 常用请求方法别名
*/
get(config) {
return this.request({ ...config, method: 'get' })
}
post(config) {
return this.request({ ...config, method: 'post' })
}
delete(config) {
return this.request({ ...config, method: 'delete' })
}
patch(config) {
return this.request({ ...config, method: 'patch' })
}
/**
* 恢复loading设置
*/
resetShowLoading() {
this.showLoading = this.instanceShowLoading
}
}
export default Request
再新建一个文件使用Request类,实例化对象就类似于 axios.create() 得到一个不同的实例。
import Request from "./request";
const request = new Request({
baseURL: 'https://pagead2.googlesyndication.com/',
timeout: '5000',
headers: {
'token': 'xxx'
},
interceptors: {
requestInterceptor(config) {
console.log(2);
return config
},
requestInterceptorCatch(error) {
console.log('requset实例请求拦截Error:', error);
return Promise.reject(error)
},
responseInterceptor(response) {
console.log(5);
return response
},
responseInterceptorCatch(error) {
console.log('requset实例响应拦截Error:', error);
return Promise.reject(error)
},
},
showLoading: true
})
export {
request
}
上述代码只实例化了一次,其实是根据业务可以实例化多次的。
再来看如何使用
import { request } from './api/request'
setTimeout(() => {
request.get({
url: 'getconfig/sodar?sv=200&tid=gda&tv=r20230306&st=env',
interceptors: {
requestInterceptor(config) {
console.log(1);
return config
},
responseInterceptor(res) {
console.log(6);
return res
}
},
showLoading: false
}).then(res => {
console.log(res, 'res');
})
.catch(err => {
console.log(err, 'err');
})
}, 3000);
setTimeout(() => {
request.get({
url: 'getconfig/sodar?sv=200&tid=gda&tv=r20230306&st=env',
interceptors: {
requestInterceptor(config) {
console.log(1);
return config
},
responseInterceptor(res) {
console.log(6);
return res
}
},
// showLoading: false
}).then(res => {
console.log(res, 'res');
})
.catch(err => {
console.log(err, 'err');
})
}, 6000);
上述代码拿着实例 request发送了两次请求,只是为了测试 showLoading噢,细节大家自己去看。
对了,代码中在三次请求拦截和三次响应拦截中console.log打印了1-6,大家可以运行起来,看看执行顺序。我这里先告诉你结论:1 2 3 4 5 6
注意this.instance.interceptors.request.use
和this.instance.interceptors.response.use
可以多次注册拦截响应函数,先注册的请求拦截后执行,先注册的响应拦截先执行。
代码里还用了 element-plus UI库,大家可以自行替换,或者删除。如果跟我一样用element-plus的话,有一个坑得注意:
ElLoading.service()
loading效果的样式必须手动引入(不知道是不是我用的插件自动按需引入element-plus的原因),当然,这个跟axios的封装没啥关系,具体怎么做看你们自己了。
手动引入代码
import 'element-plus/es/components/loading/style/css'
重点
重点理解拦截器,为什么要拦截三次。