当前位置 : 首页 > 车辆数据

Axios拦截器原理:链式调用与请求响应流程解析

<|begin▁of▁sentence|># 1. 前言 在之前的文章中,我们介绍了`axios`的拦截器,并用示意图展示了拦截器在请求从发出到接收响应过程中起的作用。拦截器本质上是一个函数数组,数组中的每个函数都有两个函数参数:`fulfilled`函数和`rejected`函数。请求拦截器的`fulfilled`函数是在请求发出之前执行,而响应拦截器的`fulfilled`函数是在响应之后执行。我们可以向`axios.interceptors.request`和`axios.interceptors.response`中的数组里面添加多个函数,那么这多个函数之间是如何串行执行的呢?另外,请求拦截器的`fulfilled`函数参数会接收请求配置对象`config`,我们可以对其修改然后返回,响应拦截器的`fulfilled`函数参数会接收响应对象`response`,我们也可以对其修改然后返回,那么它们是如何把上一个函数的返回值作为参数传给下一个函数的呢?带着这些疑问,我们接下来就分析分析拦截器的内部实现原理。 # 2. 使用方法 在分析之前,我们先来看看拦截器的使用方法,如下: ```javascript // 添加请求拦截器 axios.interceptors.request.use( function (config) { // 在发送请求之前做些什么 return config; }, function (error) { // 对请求错误做些什么 return Promise.reject(error); } ); // 添加响应拦截器 axios.interceptors.response.use( function (response) { // 对响应数据做点什么 return response; }, function (error) { // 对响应错误做点什么 return Promise.reject(error); } ); ``` 从上面代码我们可以看到: - 我们可以为`axios`添加请求拦截器和响应拦截器; - 拦截器的使用是通过`use`方法来添加的; - `use`方法接收两个参数,第一个是`fulfilled`函数,第二个是`rejected`函数; - 请求拦截器的`fulfilled`函数会接收请求配置对象`config`,我们可以对其修改然后返回;`rejected`函数会接收错误`error`,并且手动返回`Promise.reject(error)`; - 响应拦截器的`fulfilled`函数会接收响应对象`response`,我们可以对其修改然后返回;`rejected`函数会接收错误`error`,并且手动返回`Promise.reject(error)`; # 3. 拦截器管理类 从使用方法上我们可以看到:我们可以通过`axios.interceptors.request`和`axios.interceptors.response`来分别添加请求拦截器和响应拦截器,并且都是通过`use`方法来添加。那么我们可以猜测:`axios.interceptors.request`和`axios.interceptors.response`它们应该都是同一个类的实例,并且这个类上有一个`use`实例方法。 接下来,我们就去源码中一探究竟。 在`axios`源码中,有一个专门管理拦截器的类——`InterceptorManager`,它的位置在`lib/core/InterceptorManager.js`。 ## 3.1 源码分析 我们先来看看`InterceptorManager`类的源码: ```javascript function InterceptorManager() { this.handlers = []; } InterceptorManager.prototype.use = function use(fulfilled, rejected) { this.handlers.push({ fulfilled: fulfilled, rejected: rejected, }); return this.handlers.length - 1; }; InterceptorManager.prototype.eject = function eject(id) { if (this.handlers[id]) { this.handlers[id] = null; } }; InterceptorManager.prototype.forEach = function forEach(fn) { utils.forEach(this.handlers, function forEachHandler(h) { if (h !== null) { fn(h); } }); }; module.exports = InterceptorManager; ``` 我们可以看到,`InterceptorManager`类有3个实例方法:`use`、`eject`、`forEach`。 - `use`:该方法接收两个函数作为参数,用于添加拦截器函数到`handlers`数组中,并返回当前拦截器在数组中的索引; - `eject`:该方法接收一个拦截器索引作为参数,用于注销该拦截器; - `forEach`:该方法接收一个函数作为参数,用于遍历`handlers`中每一个拦截器,并将拦截器作为参数传给传入的函数; ## 3.2 在 Axios 类中实例化 在`Axios`类中,会创建`interceptors`属性,它有两个属性`request`和`response`,它们都是`InterceptorManager`类的实例,如下: ```javascript function Axios(instanceConfig) { this.defaults = instanceConfig; this.interceptors = { request: new InterceptorManager(), response: new InterceptorManager(), }; } ``` 所以,我们就可以通过`axios.interceptors.request`和`axios.interceptors.response`来分别管理请求拦截器和响应拦截器。 OK,拦截器管理类我们已经了解了,那么拦截器是如何工作的呢?即拦截器是如何在请求发出之前和响应之后依次执行的呢? # 4. 拦截器工作流程 在之前的文章中,我们介绍过,`axios`实例在真正发起请求时,调用的时`Axios.prototype.request`方法,而拦截器的工作流程就是在`Axios.prototype.request`方法中实现的,该方法的部分代码如下: ```javascript Axios.prototype.request = function request(config) { // ... // 处理参数,略... // 定义执行链 var chain = [dispatchRequest, undefined]; var promise = Promise.resolve(config); // 将请求拦截器中的函数从头部插入执行链 this.interceptors.request.forEach(function unshiftRequestInterceptors( interceptor ) { chain.unshift(interceptor.fulfilled, interceptor.rejected); }); // 将响应拦截器中的函数从尾部插入执行链 this.interceptors.response.forEach(function pushResponseInterceptors( interceptor ) { chain.push(interceptor.fulfilled, interceptor.rejected); }); // 遍历执行链 while (chain.length) { promise = promise.then(chain.shift(), chain.shift()); } return promise; }; ``` 从上面代码中,我们可以看到拦截器工作流程的具体实现: 1. 先初始化一个执行链`chain`,该链中默认有一组函数:`[dispatchRequest, undefined]`,其中`dispatchRequest`就是真正用于发送请求的函数,而`undefined`是为了给`dispatchRequest`请求失败时提供的,原因是在`promise.then`中需要传递两个参数(`onFulfilled`和`onRejected`),这里我们只关心请求发送成功的情况,所以第二个参数传了`undefined`; 2. 然后创建一个已经`resolve`的`promise`,`resolve`的值是已经处理过的请求配置对象`config`; 3. 然后遍历请求拦截器,将每个请求拦截器的`fulfilled`函数和`rejected`函数从执行链的头部插入,即`unshift`进`chain`; 4. 然后遍历响应拦截器,将每个响应拦截器的`fulfilled`函数和`rejected`函数从执行链的尾部插入,即`push`进`chain`; 5. 至此,我们已经将请求拦截器、`dispatchRequest`、响应拦截器按照顺序全部插入到执行链`chain`中,此时`chain`数组结构为: ```javascript [ 请求拦截器的fulfilled函数, 请求拦截器的rejected函数, // 第一个请求拦截器 请求拦截器的fulfilled函数, 请求拦截器的rejected函数, // 第二个请求拦截器 ..., dispatchRequest, undefined, 响应拦截器的fulfilled函数, 响应拦截器的rejected函数, // 第一个响应拦截器 响应拦截器的fulfilled函数, 响应拦截器的rejected函数, // 第二个响应拦截器 ..., ] ``` 6. 然后,我们使用`while`循环遍历执行链,每次从`chain`中取出两个元素(一个`fulfilled`函数,一个`rejected`函数)注册到`promise.then`中,这样就形成了一个链式调用,并且每次都会将上次`then`的结果作为下次`then`的输入; ## 4.1 链式调用 为了更清晰的理解这个链式调用过程,我们举个例子: 假设现在有两个请求拦截器,两个响应拦截器,如下: ```javascript // 添加第一个请求拦截器 axios.interceptors.request.use( function (config) { console.log("第一个请求拦截器成功"); return config; }, function (error) { console.log("第一个请求拦截器失败"); return Promise.reject(error); } ); // 添加第二个请求拦截器 axios.interceptors.request.use( function (config) { console.log("第二个请求拦截器成功"); return config; }, function (error) { console.log("第二个请求拦截器失败"); return Promise.reject(error); } ); // 添加第一个响应拦截器 axios.interceptors.response.use( function (response) { console.log("第一个响应拦截器成功"); return response; }, function (error) { console.log("第一个响应拦截器失败"); return Promise.reject(error); } ); // 添加第二个响应拦截器 axios.interceptors.response.use( function (response) { console.log("第二个响应拦截器成功"); return response; }, function (error) { console.log("第二个响应拦截器失败"); return Promise.reject(error); } ); ``` 那么,执行链`chain`就为: ```javascript [ function (config) { // 第一个请求拦截器的fulfilled函数 console.log("第一个请求拦截器成功"); return config; }, function (error) { // 第一个请求拦截器的rejected函数 console.log("第一个请求拦截器失败"); return Promise.reject(error); }, function (config) { // 第二个请求拦截器的fulfilled函数 console.log("第二个请求拦截器成功"); return config; }, function (error) { // 第二个请求拦截器的rejected函数 console.log("第二个请求拦截器失败"); return Promise.reject(error); }, dispatchRequest, undefined, function (response) { // 第一个响应拦截器的fulfilled函数 console.log("第一个响应拦截器成功"); return response; }, function (error) { // 第一个响应拦截器的rejected函数 console.log("第一个响应拦截器失败"); return Promise.reject(error); }, function (response) { // 第二个响应拦截器的fulfilled函数 console.log("第二个响应拦截器成功"); return response; }, function (error) { // 第二个响应拦截器的rejected函数 console.log("第二个响应拦截器失败"); return Promise.reject(error); }, ] ``` 然后,我们使用`while`循环遍历执行链,每次取出两个注册到`promise.then`中: ```javascript // 初始promise var promise = Promise.resolve(config); // 第一次while循环 promise = promise.then(chain.shift(), chain.shift()); // 此时promise为: promise.then( function (config) { console.log("第一个请求拦截器成功"); return config; }, function (error) { console.log("第一个请求拦截器失败"); return Promise.reject(error); } ); // 第二次while循环 promise = promise.then(chain.shift(), chain.shift()); // 此时promise为: promise .then( function (config) { console.log("第一个请求拦截器成功"); return config; }, function (error) { console.log("第一个请求拦截器失败"); return Promise.reject(error); } ) .then( function (config) { console.log("第二个请求拦截器成功"); return config; }, function (error) { console.log("第二个请求拦截器失败"); return Promise.reject(error); } ); // 第三次while循环 promise = promise.then(chain.shift(), chain.shift()); // 此时promise为: promise .then( function (config) { console.log("第一个请求拦截器成功"); return config; }, function (error) { console.log("第一个请求拦截器失败"); return Promise.reject(error); } ) .then( function (config) { console.log("第二个请求拦截器成功"); return config; }, function (error) { console.log("第二个请求拦截器失败"); return Promise.reject(error); } ) .then(dispatchRequest, undefined); // 第四次while循环 promise = promise.then(chain.shift(), chain.shift()); // 此时promise为: promise .then( function (config) { console.log("第一个请求拦截器成功"); return config; }, function (error) { console.log("第一个请求拦截器失败"); return Promise.reject(error); } ) .then( function (config) { console.log("第二个请求拦截器成功"); return config; }, function (error) { console.log("第二个请求拦截器失败"); return Promise.reject(error); } ) .then(dispatchRequest, undefined) .then( function (response) { console.log("第一个响应拦截器成功"); return response; }, function (error) { console.log("第一个响应拦截器失败"); return Promise.reject(error); } ); // 第五次while循环 promise = promise.then(chain.shift(), chain.shift()); // 此时promise为: promise .then( function (config) { console.log("第一个请求拦截器成功"); return config; }, function (error) { console.log("第一个请求拦截器失败"); return Promise.reject(error); } ) .then( function (config) { console.log("第二个请求拦截器成功"); return config; }, function (error) { console.log("第二个请求拦截器失败"); return Promise.reject(error); } ) .then(dispatchRequest, undefined) .then( function (response) { console.log("第一个响应拦截器成功"); return response; }, function (error) { console.log("第一个响应拦截器失败"); return Promise.reject(error); } ) .then( function (response) { console.log("第二个响应拦截器成功"); return response; }, function (error) { console.log("第二个响应拦截器失败"); return Promise.reject(error); } ); ``` 从上面代码中,我们可以清晰的看到:请求拦截器从前往后执行,响应拦截器从后往前执行,并且上一个拦截器的返回值会作为下一个拦截器的输入。 # 5. 总结 OK,以上就是拦截器的实现原理,我们总结一下: 1. 拦截器分为请求拦截器和响应拦截器,它们分别有`fulfilled`函数和`rejected`函数; 2. 拦截器的管理是通过`InterceptorManager`类来管理的,它有三个实例方法:`use`、`eject`、`forEach`; 3. 拦截器的工作流程是在`Axios.prototype.request`方法中实现的; 4. 在`Axios.prototype.request`方法中,会创建一个执行链`chain`,默认包含`dispatchRequest`和`undefined`; 5. 然后遍历请求拦截器,将每个拦截器的`fulfilled`函数和`rejected`函数从执行链的头部插入; 6. 然后遍历响应拦截器,将每个拦截器的`fulfilled`函数和`rejected`函数从执行链的尾部插入; 7. 然后使用`while`循环遍历执行链,每次取出两个函数注册到`promise.then`中,形成一个链式调用; 8. 这样,请求拦截器从前往后执行,响应拦截器从后往前执行,并且上一个拦截器的返回值会作为下一个拦截器的输入;

栏目列表