一、拦截器介绍
先看下官方文档对拦截器的介绍:
You can intercept requests or responses before they are handled by then or catch.
即我们可以在axios
返回的promise
定型(resolve
)之前拦截请求和响应。这里是文档的示例代码:
// Add a request interceptor
axios.interceptors.request.use(function (config) {
// Do something before request is sent
return config;
}, function (error) {
// Do something with request error
return Promise.reject(error);
});
// Add a response interceptor
axios.interceptors.response.use(function (response) {
// Any status code that lie within the range of 2xx cause this function to trigger
// Do something with response data
return response;
}, function (error) {
// Any status codes that falls outside the range of 2xx cause this function to trigger
// Do something with response error
return Promise.reject(error);
});
// If you need to remove an interceptor later you can.
const myInterceptor = axios.interceptors.request.use(function () {/*...*/});
axios.interceptors.request.eject(myInterceptor);
还有些功能示例代码并没有告诉我们,我们去github
上看下详细的拦截器文档,看完之后,我对这部分功能点做了个总结:
-
Axios
可以添加请求、响应拦截器; - 请求拦截器可以在请求被发出之前做一些处理;
- 响应拦截器可以在用户代码获取到响应之前对响应做一些处理;
-
Axios
可以移除拦截器; - 用户可以决定请求拦截器是同步执行还是异步执行;
- 用户可以决定拦截器是否执行。
下面,我们将围绕以上6个功能进行源码阅读。
二、拦截器源码阅读
2.1 添加拦截器
文档指出我们可以通过axios.interceptors.request.use
和axios.interceptors.response.use
来添加拦截器,那么我们一步一步看这个功能是如何实现的。
axios.interceptors
axios
上扩展了Axios
实例的属性,所以我们在Axios.js
中找到了axios.interceptors
的来源:
传送门:./lib/core/Axios.js
/**
* Create a new instance of Axios
*
* @param {Object} instanceConfig The default config for the instance
*/
function Axios(instanceConfig) {
this.defaults = instanceConfig;
this.interceptors = {
request: new InterceptorManager(),
response: new InterceptorManager()
};
}
所以axios.interceptors
里面是两个拦截器管理对象
InterceptorManager
我们直接找到拦截器管理器的源码:
传送门:./lib/core/InterceptorManager.js
function InterceptorManager() {
this.handlers = [];
}
/**
* Add a new interceptor to the stack
*
* @param {Function} fulfilled The function to handle `then` for a `Promise`
* @param {Function} rejected The function to handle `reject` for a `Promise`
*
* @return {Number} An ID used to remove interceptor later
*/
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;
};
InterceptorManager
内部只有一个实例属性handlers
,引用的是一个空数组;InterceptorManager.prototype.use
接受三个参数,分别是fulfilled callback
、rejected callback
和options
,然后把这三个参数包装为一个对象push
到handlers
中,并返回在handlers
中的索引。这便是添加拦截器的过程。
2.2 拦截器的工作原理
通过上篇文章:Axios源码阅读(一):核心功能源码,我们知道Axios.prototype.request
中描述了拦截器是如何工作的:
传送门:./lib/core/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) {
// 如果 runWhen 的结果是 false ,那么不执行这个拦截器
return;
}
// 默认拦截器是异步的,并且只有所有的拦截器都是同步的,才会同步执行
synchronousRequestInterceptors = synchronousRequestInterceptors && interceptor.synchronous;
requestInterceptorChain.unshift(interceptor.fulfilled, interceptor.rejected);
});
var responseInterceptorChain = [];
this.interceptors.response.forEach(function pushResponseInterceptors(interceptor) {
responseInterceptorChain.push(interceptor.fulfilled, interceptor.rejected);
});
// 如果拦截器是异步执行
var promise;
if (!synchronousRequestInterceptors) {
// 初始化一个执行链
var chain = [dispatchRequest, undefined];
// 把请求拦截器放在链首
Array.prototype.unshift.apply(chain, requestInterceptorChain);
// 把响应拦截器放在链尾
chain = chain.concat(responseInterceptorChain);
promise = Promise.resolve(config);
while (chain.length) {
promise = promise.then(chain.shift(), chain.shift());
}
return promise;
}
// 如果拦截器是同步执行
var newConfig = config;
while (requestInterceptorChain.length) {
// 循环执行完所有的请求拦截器
var onFulfilled = requestInterceptorChain.shift();
var onRejected = requestInterceptorChain.shift();
try {
newConfig = onFulfilled(newConfig);
} catch (error) {
onRejected(error);
break;
}
}
try {
promise = dispatchRequest(newConfig);
} catch (error) {
return Promise.reject(error);
}
// 把所有的响应拦截器追加到 dispatchRequest 返回的 promise 后面
while (responseInterceptorChain.length) {
promise = promise.then(responseInterceptorChain.shift(), responseInterceptorChain.shift());
}
return promise;
小结
在这里总结下拦截器的执行步骤:
- 发起请求前声明请求拦截器数组和响应拦截器数组;
- 遍历
axios
实例上的请求拦截器,执行runWhen
(如果存在的话),将结果为true
的拦截器的callback
压入请求拦截器数组中,并判断拦截器是同步还是异步; - 将所有的响应拦截器的
callback
压入响应拦截器数组中; - 如果所有的,注意是所有的请求拦截器都是同步的(将在下文论证这个观点),那么就先循环执行完所有的请求拦截器,然后返回从
dispatchRequest
开始的promise
;否则,所有的请求拦截器都是异步执行,即返回第一个拦截器执行的promise
;
2.3 移除拦截器
示例代码中通过axios.interceptors.request.eject
可以移除拦截器,我们找到相应代码:
传送门:./lib/core/InterceptorManager.js
/**
* Remove an interceptor from the stack
*
* @param {Number} id The ID that was returned by `use`
*/
InterceptorManager.prototype.eject = function eject(id) {
if (this.handlers[id]) {
this.handlers[id] = null;
}
};
这里的实现很简单,通过将handlers
数组中对应的拦截器置为null
就可以了,那这个索引是从哪里来的呢?其实来源于use
方法会返回拦截器在handlers
数组中的索引。
2.4 拦截器的同步执行和条件执行
同步执行
InterceptorManager.prototype.use
接收的第三个参数为一个对象,在该对象上可以配置拦截器是同步还是异步执行,但是每个拦截器都有这个配置项,如果配置项不同的话,会产生什么后果呢?比如有的拦截器配置的是同步执行,有的拦截器配置的是异步执行,那拦截器最终是如何执行的呢?
通过源码,我们不难发现只有在所有的拦截器都是同步执行的情况下,拦截器才会同步执行,否则会异步执行。这里我们实验论证下:
axios.interceptors.request.use(config => {
console.log('请求拦截器1');
return config;
}, error => {}, {
synchronous: true
});
axios.interceptors.request.use(config => {
console.log('请求拦截器2');
return config;
}, error => {}, {
synchronous: false
});
axios.get('/api').then(res => {
console.log(res);
})
console.log('同步代码执行');
执行结果:
axios.interceptors.request.use(config => {
console.log('请求拦截器1');
return config;
}, error => {}, {
synchronous: true
});
axios.interceptors.request.use(config => {
console.log('请求拦截器2');
return config;
}, error => {}, {
synchronous: true
});
console.log('同步代码执行');
执行结果:
通过以上实验可以得出:只有当所有拦截器都是同步执行时,拦截器才会同步执行,否则都会异步执行。
条件执行
当拦截器需要条件执行的时候,只需要在use
的时候配置好runWhen
就行了
axios.interceptors.request.use(config => {return config}, err => {}, {
runWhen: config => {
return config.headers.token ? true : false
}
})