Axios源码:拦截请求和响应的实现

631 阅读3分钟

Axios

简介

Axios 是一个基于 Promise 的 HTTP 客户端,同时支持浏览器和Node.js. 拥有以下特性:

  • 支持浏览器发送AJAX请求
  • 支持Node.js发送 HTTP 请求
  • 支持Promise
  • 拦截请求和响应
  • 转换请求和响应的数据
  • 取消请求
  • 自动转换JSON数据
  • 客户端支持XSRF攻击

拦截请求和响应简介

前端向后端发送请求时,有很多配置参数以及请求返回的处理过程是相同的。假如在每一个HTTP请求中都配置相同的参数和加入请求处理函数,代码就会比较的ugly,而且不易维护。

想象一下下面的场景

  • 应用基于JWT进行身份验证,需要在每个HTTP请求的 Header上添加token
  • 对于返回的错误信息,做统一的错误处理

针对上面的问题场景,Axios为我们提供了一种解决方案,拦截器。

使用拦截器

// 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);
});

实现原理

问题

  • 拦截器处理函数什么时候触发?

axios实例对象

通过axios.interceptors.request.use()调用拦截器,得知拦截器是axios实例的属性。

通过下面的代码,Axios 构造函数上有属性interceptorsinterceptorsrequestresponse属性都是InterceptorManager类的实例对象

// lib/core/Axios.js

function Axios(instanceConfig) {
  this.defaults = instanceConfig;
  this.interceptors = {
    request: new InterceptorManager(),
    response: new InterceptorManager()
  };
}

拦截处理函数收集

InterceptorManager类用于生成拦截器实例。 use方法主要是用于将promise成功状态和失败状态的处理函数添加到handlers

// 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) {
  this.handlers.push({
    fulfilled: fulfilled,
    rejected: rejected
  });
  return this.handlers.length - 1;
};

拦截请求和响应触发

在调用axios的请求方法时,会触发拦截器 axios支持的request method如下, 这些方法最后都会调用axios.request.axios.request触发请求和响应拦截器和向服务器端发送HTTP请求。

  • axios.request(config)
  • axios.get(url[, config])
  • axios.delete(url[, config])
  • axios.head(url[, config])
  • axios.options(url[, config])
  • axios.post(url[, data[, config]])
  • axios.put(url[, data[, config]])
  • axios.patch(url[, data[, config]])
// lib/core/Axios.js

// Provide aliases for supported request methods
utils.forEach(['delete', 'get', 'head', 'options'], function forEachMethodNoData(method) {
  /*eslint func-names:0*/
  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) {
  /*eslint func-names:0*/
  Axios.prototype[method] = function(url, data, config) {
    return this.request(mergeConfig(config || {}, {
      method: method,
      url: url,
      data: data
    }));
  };
});

axios.request

dispatchRequest 用于发送HTTP请求,请求拦截器处理函数放在该方法之前,响应拦截器处理函数放在该方法之后。

// lib/core/Axios.js
/**
 * Dispatch a request
 *
 * @param {Object} config The config specific for this request (merged with this.defaults)
 */
Axios.prototype.request = function request(config) {
  /*eslint no-param-reassign:0*/
  // Allow for axios('example/url'[, config]) a la fetch API
  if (typeof config === 'string') {
    config = arguments[1] || {};
    config.url = arguments[0];
  } else {
    config = config || {};
  }

  config = mergeConfig(this.defaults, config);

  // Set config.method
  if (config.method) {
    config.method = config.method.toLowerCase();
  } else if (this.defaults.method) {
    config.method = this.defaults.method.toLowerCase();
  } else {
    config.method = 'get';
  }

  // Hook up interceptors middleware
  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;
};

参考文献