Axios源码阅读(二):拦截器

一、拦截器介绍

先看下官方文档对拦截器的介绍:

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上看下详细的拦截器文档,看完之后,我对这部分功能点做了个总结:

  1. Axios可以添加请求、响应拦截器;
  2. 请求拦截器可以在请求被发出之前做一些处理;
  3. 响应拦截器可以在用户代码获取到响应之前对响应做一些处理;
  4. Axios可以移除拦截器;
  5. 用户可以决定请求拦截器是同步执行还是异步执行;
  6. 用户可以决定拦截器是否执行。

下面,我们将围绕以上6个功能进行源码阅读。

二、拦截器源码阅读

2.1 添加拦截器

文档指出我们可以通过axios.interceptors.request.useaxios.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 callbackrejected callbackoptions,然后把这三个参数包装为一个对象pushhandlers中,并返回在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;

小结

在这里总结下拦截器的执行步骤:

  1. 发起请求前声明请求拦截器数组和响应拦截器数组;
  2. 遍历axios实例上的请求拦截器,执行runWhen(如果存在的话),将结果为true的拦截器的callback压入请求拦截器数组中,并判断拦截器是同步还是异步;
  3. 将所有的响应拦截器的callback压入响应拦截器数组中;
  4. 如果所有的,注意是所有的请求拦截器都是同步的(将在下文论证这个观点),那么就先循环执行完所有的请求拦截器,然后返回从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
    }
})

你可能感兴趣的:(Axios源码阅读(二):拦截器)