axios源码解析-拦截器
之前我们介绍了Axios这个对象,其中,Axios的拦截器部分很简单,只是给请求和响应各创建了一个InterceptorManager的实例,那么,今天我们就来看看InterceptorManager究竟干了什么事情
function InterceptorManager() {
// 处理方法作为数组依次执行
this.handlers = [];
}
我们可以看到,InterceptorManager这个构造函数的结构页很简单,只是定义了一个用来处理的数组而已,有了前面Axios的基础,我们可能很容易想到,拦截器是不是用数组去保存一组函数呢?那么,接下来他调用use方法的部分就有趣了
InterceptorManager.prototype.use = function use(fulfilled, rejected, options) {
this.handlers.push({
// 成功会调
fulfilled: fulfilled,
// 失败回调
rejected: rejected,
// 是否为同步函数
synchronous: options ? options.synchronous : false,
runWhen: options ? options.runWhen : null
});
return this.handlers.length - 1;
// 注意这里返回的是一个下标,函数利用下标值来完成对拦截器的删除或调用
};
我们发现,use这个方法 本质上只是只是将拦截器的成功/失败回调,配置保存到一个数组里,并不进行任何处理。其实,拦截器的处理是跟dispatchRequest一起的,都被放入执行链进行执行
我们再仔细看Axios.js中的代码
// filter out skipped interceptors
// 拦截器链
var requestInterceptorChain = [];
// 是否为同步函数
var synchronousRequestInterceptors = true;
this.interceptors.request.forEach(function unshiftRequestInterceptors(interceptor) {
if (typeof interceptor.runWhen === 'function' && interceptor.runWhen(config) === false) {
return;
}
// 确保每个请求拦截器都是同步的,最终的拦截器才是同步的
synchronousRequestInterceptors = synchronousRequestInterceptors && interceptor.synchronous;
// 将每个请求拦截器的成功回调和失败回调绑定在执行链的最前端
requestInterceptorChain.unshift(interceptor.fulfilled, interceptor.rejected);
});
var responseInterceptorChain = [];
// 注意这里的foreach方法是拦截器原型链上的foreach方法
// 这个方法内部会遍历所有拦截器
this.interceptors.response.forEach(function pushResponseInterceptors(interceptor) {
// 将每个响应拦截器加入到执行链的最后端
responseInterceptorChain.push(interceptor.fulfilled, interceptor.rejected);
});
可以看出,最后被加入执行链的,也仅仅是拦截器中的两个回调函数,而 synchronous这个配置用于判断函数是否是同步函数,拦截器函数的执行,是被放在执行链调用的部分进行的
另一个有意思的点,是use这个方法返回的居然是数组的下标,在此之前,我一直意味返回的应该是一个函数或对象之类的,那么,为什么他要返回一个下标呢,其实这是为了简化删除拦截器的方法而准备的
//通过use方法拿到id,利用id删除对应元素
InterceptorManager.prototype.eject = function eject(id) {
if (this.handlers[id]) {
this.handlers[id] = null;
}
};
我们可以看到,我们删除一个拦截器,本质上就是将拦截器数组对应下标的元素置空,那么也就是说,只要我们知道了这个拦截器的索引,就可以通过索引找到并删除这个拦截器了。因此,我们在创建拦截器时,只要用一个变量保存这个拦截器在数组中的下标,删除时就可以通过相同的变量对其进行删除
另外注意一点,就是官网上拦截器实例的返回值
// 添加请求拦截器
axios.interceptors.request.use(function (config) {
// 在发送请求之前做些什么
return config;
}, function (error) {
// 对请求错误做些什么
return Promise.reject(error);
});
// 添加响应拦截器
axios.interceptors.response.use(function (response) {
// 2xx 范围内的状态码都会触发该函数。
// 对响应数据做点什么
return response;
}, function (error) {
// 超出 2xx 范围的状态码都会触发该函数。
// 对响应错误做点什么
return Promise.reject(error);
});
我们可以看到,请求和响应拦截器的成功回调都是链式对请求响应数据进行修改,因此每次调用都返回了更新后的配置,让下一级继续调用,链式调用的传参也是在这里进行的。而失败的请求,则是返回一个Promise的失败回调,并将参数传入。这里要注意一下
在InterceptorsManger的原型链上,还挂载了一个对拦截器进行统一操作的方法foreach
InterceptorManager.prototype.forEach = function forEach(fn) {
utils.forEach(this.handlers, function forEachHandler(h) {
if (h !== null) {
// 对于数组的每一项,只要有值就调用该函数(确认还没有被销毁)
// 这里的fn就是将所有拦截器放入执行链的push
fn(h);
}
});
};
这里外层的foreach主要是将函数作为参数进行传递,而内部utils上的foreach,则是遍历这个数组的每一项,并将每一项作为参数让外部传进来的函数进行调用。这个方法在Axios.js中的应用是将所有的拦截器方法放入执行链中,我们同样可以调用它对拦截器上的所有方法做一些统一处理
//在Axios.js中的调用示例
this.interceptors.response.forEach(function
pushResponseInterceptors(interceptor) {
// 将每个响应拦截器加入到执行链的最后端
responseInterceptorChain.push(interceptor.fulfilled, interceptor.rejected);
});
拦截器的实现,主要是用统一模型,控制拦截器的注册,销毁,执行