为什么要配置axios请求
- 我们发现在与前后端联调的时候,一般都有打开Loading,关闭loading的逻辑,还有请求出错的处理,按照don’t repeat yourself 的设计原则,我们应该把他们抽离出来统一处理。
- 权限验证的统一添加
如何配置
- 要实现这个问题,我们要用到axios的拦截器 readme, 在请求前打开loading,响应后关闭loading并且进行错误的处理;
- 本例中loading和消息提示都是使用的element,可以根据自己需求自定义
- 具体代码如下
import axios from 'axios'
import store from '@/model'
import {Message} from 'element-ui'
import {Loading} from 'element-ui'
import router from '@/router'
// 打开关闭Loading类
class LoadingService {
count = 0
service = null
openLoading () {
this.count++
this.service = Loading.service();
}
closeLoading () {
setTimeout(() => {
this.count--
if (this.count <= 0) {
this.service.close();
}
}, 0) // 添加这段代码的原因是多个链式请求页面会出现loading闪烁的情况,使用setTimeout 避免频繁的开启关闭loading
}
}
const loadingInstance = new LoadingService();
const successCode = 200 // 前后端协调的正确响应状态
const http = axios.create({
timeout: 100000, // 接口等待时间
withCredentials: true, //跨域设置
headers: {
'Content-Type': 'application/json;charset=utf-8',
},
});
const requestInterceptor = http.interceptors.request.use(function (config) {
let {user: {tokenInfo:tokenInfo}} = store.state
tokenInfo && tokenInfo.access_token && (config.headers.Authorization = `Bearer ${tokenInfo.access_token}`)
loadingInstance.openLoading()
return Promise.resolve(config)
}, function (error) {
Message.error(error);
return Promise.reject(error)
})
const responseInterceptor = http.interceptors.response.use(function (response) {
loadingInstance.closeLoading();
try {
if (response.data.code != successCode) {
Message.error(response.data.msg)
}
} catch (e) {
Message.error(e)
}
return Promise.resolve(response.data);
}, function ({response}) {
loadingInstance.closeLoading()
let errorMsg = ''
switch (response.status) {
case 400 : errorMsg += '用户请求错误,请检查发送的参数'; break;
case 401 : router.push({name: 'login'}); errorMsg = '登录权限验证过期,请重新登录';break;
case 404 : errorMsg += '请求接口不存在'; break;
case 500 : errorMsg += '服务器发生错误'; break;
case 504 : errorMsg += '服务器发生错误'; break;
default:
errorMsg += response.statusText
break;
}
Message.error(errorMsg)
return Promise.reject(response)
})
export default http
axios拦截器的原理
- GitHub 搜索axios,在源码中先找到Axios类,在lib/core/axios.js,可以发现实例中的interceptors中的请求拦截是InterceptorManager的实例,代码如下
function Axios(instanceConfig) {
this.defaults = instanceConfig;
this.interceptors = {
request: new InterceptorManager(),
response: new InterceptorManager()
};
}
- InterceptorManager但从字面理解是一个拦截管理器,下面我们带着探索来看下InterceptorManager的内部实现,里面主要实现了:use(fullfilled,reject)// 添加一个拦截器 返回一个拦截器id;eject(id) // 移除指定拦截器;forEach(fn) //访问迭代器
- 现在知道了上面配置中用到的use就是在各自持有的数组中压入了一个拦截器,对于怎么最终是怎么触发的还需要继续深入探索,接下来看下axios发请求的逻辑,感觉拦截器应该是在这里面发挥了作用,在lib/core/axios.js中找到如下代码,可以发现,axios在原型上挂在get,post等方法的时候,对公共部分进行了抽离,记忆中这个好像是桥接模式
utils.forEach(['delete', 'get', 'head', 'options'], function forEachMethodNoData(method) {
Axios.prototype[method] = function(url, config) {
return this.request(mergeConfig(config || {}, {
method: method,
url: url,
data: (config || {}).data
}));
};
});
utils.forEach(['post', 'put', 'patch'], function forEachMethodWithData(method) {
Axios.prototype[method] = function(url, data, config) {
return this.request(mergeConfig(config || {}, {
method: method,
url: url,
data: data
}));
};
});
关键的代码还是在request中,最终我们发现拦截器是一个职责链模式的实现,但是这种异步链式调用还是可以借鉴的
Axios.prototype.request = function request(config) {
... 省去参数适配
var chain = [dispatchRequest, undefined]; // dispatchRequest 是发送请求的封装,返回Promise
var promise = Promise.resolve(config);
this.interceptors.request.forEach(function unshiftRequestInterceptors(interceptor){
chain.unshift(interceptor.fulfilled, interceptor.rejected);
})
while (chain.length) { // promise链式调用
promise = promise.then(chain.shift(), chain.shift());
}
return promise
}