前言

这应该是一个大多数都常用的请求库,因为它可以支持多种配置,跨平台实现,返回promise进行链式调用.完全过一遍源码可以提升自己对请求库的理解知识

axios源码系列(一) --- 目录结构和工具函数
axios源码系列(二) --- 适配器内部
axios源码系列(三) --- 默认配置和取消请求
axios源码系列(四) --- Axios和dispatchRequest与拦截器

axios创建

这就是Axios库暴露出来的使用方法

axios/lib/axios.js

var utils = require('./utils');var bind = require('./helpers/bind');var Axios = require('./core/Axios');var mergeConfig = require('./core/mergeConfig');var defaults = require('./defaults');/** * Create an instance of Axios * * @param {Object} defaultConfig The default config for the instance * @return {Axios} A new instance of Axios */function createInstance(defaultConfig) {  var context = new Axios(defaultConfig);  var instance = bind(Axios.prototype.request, context);  // Copy axios.prototype to instance  utils.extend(instance, Axios.prototype, context);  // Copy context to instance  utils.extend(instance, context);  return instance;}// Create the default instance to be exportedvar axios = createInstance(defaults);

axios实例实际上就是由Axios构造函数创建出来,再将原型上的request的this指向axios实例,然后调用utils.extend扩展函数将axios.prototypecontext附加在axios实例上,

上面可能有疑问的点在于这两点,好像显得多此一举

// Copy axios.prototype to instanceutils.extend(instance, Axios.prototype, context);// Copy context to instanceutils.extend(instance, context);

但是在这里的extend里不单单是做扩展,还额外增加了绑定this指向的功能,所以在两次操作实现目的不一样

  1. instance是被扩展对象,Axiox.prototype是用于扩展的对象,context是作为扩展对象相关functionthis指定对象
  2. instance依然是被扩展对象,但这次context是用于扩展的对象
// Expose Axios class to allow class inheritanceaxios.Axios = Axios;// Factory for creating new instancesaxios.create = function create(instanceConfig) {  return createInstance(mergeConfig(axios.defaults, instanceConfig));};

我们看看内部是怎么合并配置的

axios/lib/core/mergeConfig.js

这是负责合并配置的方法

var utils = require('../utils');/** * Config-specific merge-function which creates a new config-object * by merging two configuration objects together. * * @param {Object} config1 * @param {Object} config2 * @returns {Object} New object resulting from merging config2 to config1 */module.exports = function mergeConfig(config1, config2) {  // eslint-disable-next-line no-param-reassign  config2 = config2 || {};  var config = {};  var valueFromConfig2Keys = ['url', 'method', 'params', 'data'];  var mergeDeepPropertiesKeys = ['headers', 'auth', 'proxy'];  var defaultToConfig2Keys = [    'baseURL', 'url', 'transformRequest', 'transformResponse', 'paramsSerializer',    'timeout', 'withCredentials', 'adapter', 'responseType', 'xsrfCookieName',    'xsrfHeaderName', 'onUploadProgress', 'onDownloadProgress',    'maxContentLength', 'validateStatus', 'maxRedirects', 'httpAgent',    'httpsAgent', 'cancelToken', 'socketPath'  ];  utils.forEach(valueFromConfig2Keys, function valueFromConfig2(prop) {    if (typeof config2[prop] !== 'undefined') {      config[prop] = config2[prop];    }  });  utils.forEach(mergeDeepPropertiesKeys, function mergeDeepProperties(prop) {    if (utils.isObject(config2[prop])) {      config[prop] = utils.deepMerge(config1[prop], config2[prop]);    } else if (typeof config2[prop] !== 'undefined') {      config[prop] = config2[prop];    } else if (utils.isObject(config1[prop])) {      config[prop] = utils.deepMerge(config1[prop]);    } else if (typeof config1[prop] !== 'undefined') {      config[prop] = config1[prop];    }  });  utils.forEach(defaultToConfig2Keys, function defaultToConfig2(prop) {    if (typeof config2[prop] !== 'undefined') {      config[prop] = config2[prop];    } else if (typeof config1[prop] !== 'undefined') {      config[prop] = config1[prop];    }  });  var axiosKeys = valueFromConfig2Keys    .concat(mergeDeepPropertiesKeys)    .concat(defaultToConfig2Keys);  var otherKeys = Object    .keys(config2)    .filter(function filterAxiosKeys(key) {      return axiosKeys.indexOf(key) === -1;    });  utils.forEach(otherKeys, function otherKeysDefaultToConfig2(prop) {    if (typeof config2[prop] !== 'undefined') {      config[prop] = config2[prop];    } else if (typeof config1[prop] !== 'undefined') {      config[prop] = config1[prop];    }  });  return config;};

从调用的方式可得,config1是axios的默认配置,config2是用户自定义的配置

里面默认有三种不同优先级的配置

  • valueFromConfig2Keys: 指定只要config2内的相关属性不为undefined则取config2的值,这里根本不考虑config1配置
  • mergeDeepPropertiesKeys: 深层请求头结构,config2优先级高于config1,先处理对象结构再处理其他非undefined类型
  • defaultToConfig2Keys: 可直接赋值的请求头列表,config2优先级高于config1

遍历处理得出上面三种配置之后,我们可以得到

  • axiosKeys: config1和config2含有上面三种默认配置合并后得到的映射数组
  • otherKeys: 过滤出config2中axiosKeys里不包含的配置属性,基本是非常用或者自定义的字段,同样不考虑config1的情况

最后就是将otherKeys的相关配置也合并到最终结果并返回该结果

// Expose Cancel & CancelTokenaxios.Cancel = require('./cancel/Cancel');axios.CancelToken = require('./cancel/CancelToken');axios.isCancel = require('./cancel/isCancel');// Expose all/spreadaxios.all = function all(promises) {  return Promise.all(promises);};axios.spread = require('./helpers/spread');module.exports = axios;// Allow use of default import syntax in TypeScriptmodule.exports.default = axios;

在axios实例上添加方法,我们先看spread是有什么作用

axios/lib/helpers/spread.js

/** * Syntactic sugar for invoking a function and expanding an array for arguments. * * Common use case would be to use `Function.prototype.apply`. * *  ```js *  function f(x, y, z) {} *  var args = [1, 2, 3]; *  f.apply(null, args); *  ``` * * With `spread` this example can be re-written. * *  ```js *  spread(function(x, y, z) {})([1, 2, 3]); *  ``` * * @param {Function} callback * @returns {Function} */module.exports = function spread(callback) {  return function wrap(arr) {    return callback.apply(null, arr);  };};

只是一句语法糖,没有黑魔法

Axios核心构造函数

axios/lib/core/Axios.js

这是Axios的构造函数

var utils = require('./../utils');var buildURL = require('../helpers/buildURL');var InterceptorManager = require('./InterceptorManager');var dispatchRequest = require('./dispatchRequest');var mergeConfig = require('./mergeConfig');/** * 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实例默认会保存配置,并且内置拦截器分别给请求和响应

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

request分两部分逻辑

  1. 支持首个参数传入请求地址的调用方式,合并默认配置和传入配置
  2. chain作为钩子中间件进行链式调用,最终变成

    [    this.interceptors.request.fulfilled, this.interceptors.request.rejected,// 请求拦截    dispatchRequest, undefined,// 发起请求    this.interceptors.response.fulfilled, this.interceptors.response.rejected,// 响应拦截]
  3. 使用while链式调用chain的函数,完成从发起到接收请求的完整流程
Axios.prototype.getUri = function getUri(config) {  // 合并配置  config = mergeConfig(this.defaults, config);  // 返回拼装URL  return buildURL(config.url, config.params, config.paramsSerializer).replace(/^\?/, '');};// Provide aliases for supported request methodsutils.forEach(['delete', 'get', 'head', 'options'], function forEachMethodNoData(method) {  /*eslint func-names:0*/  Axios.prototype[method] = function(url, config) {    return this.request(utils.merge(config || {}, {      method: method,      url: url    }));  };});utils.forEach(['post', 'put', 'patch'], function forEachMethodWithData(method) {  /*eslint func-names:0*/  Axios.prototype[method] = function(url, data, config) {    return this.request(utils.merge(config || {}, {      method: method,      url: url,      data: data    }));  };});module.exports = Axios;

遍历所有请求方式调用内置的request方法

拦截器

axios/lib/core/InterceptorManager.js

定义拦截器的构造函数

var utils = require('./../utils');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;};/** * 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;  }};/** * Iterate over all the registered interceptors * * This method is particularly useful for skipping over any * interceptors that may have become `null` calling `eject`. * * @param {Function} fn The function to call for each interceptor */InterceptorManager.prototype.forEach = function forEach(fn) {  utils.forEach(this.handlers, function forEachHandler(h) {    if (h !== null) {      fn(h);    }  });};module.exports = InterceptorManager;

整个函数其实只做了几件事:

  1. 生成实例,内置一个handlers队列
  2. 原型绑定use注册方法,队列传入包含满足和失败函数方法的拦截对象,返回该对象在队列中的id(实际上就是索引值)
  3. 原型绑定eject解绑方法,队列移除指定id的拦截对象
  4. 原型绑定forEach遍历方法,迭代所有已注册的拦截器

拦截器示例

// 添加请求拦截器const reqInterceptor = axios.interceptors.request.use(function (config) {    // 在发送请求之前做些什么    return config;  }, function (error) {    // 对请求错误做些什么    return Promise.reject(error);  });// 添加响应拦截器const resInterceptor = axios.interceptors.response.use(function (response) {    // 对响应数据做点什么    return response;  }, function (error) {    // 对响应错误做点什么    return Promise.reject(error);  });  // 移除axios.interceptors.request.eject(reqInterceptor);axios.interceptors.request.eject(resInterceptor);

调度请求

axios/lib/core/dispatchRequest.js

这是负责调度请求的函数,返回Promise.

var utils = require('./../utils');var transformData = require('./transformData');var isCancel = require('../cancel/isCancel');var defaults = require('../defaults');/** * Throws a `Cancel` if cancellation has been requested. */function throwIfCancellationRequested(config) {  if (config.cancelToken) {    config.cancelToken.throwIfRequested();  }}/** * Dispatch a request to the server using the configured adapter. * * @param {object} config The config that is to be used for the request * @returns {Promise} The Promise to be fulfilled */module.exports = function dispatchRequest(config) {  throwIfCancellationRequested(config);  // Ensure headers exist  config.headers = config.headers || {};  // Transform request data  config.data = transformData(    config.data,    config.headers,    config.transformRequest  );  // Flatten headers  config.headers = utils.merge(    config.headers.common || {},    config.headers[config.method] || {},    config.headers || {}  );  utils.forEach(    ['delete', 'get', 'head', 'post', 'put', 'patch', 'common'],    function cleanHeaderConfig(method) {      delete config.headers[method];    }  );  var adapter = config.adapter || defaults.adapter;  return adapter(config).then(function onAdapterResolution(response) {    throwIfCancellationRequested(config);    // Transform response data    response.data = transformData(      response.data,      response.headers,      config.transformResponse    );    return response;  }, function onAdapterRejection(reason) {    if (!isCancel(reason)) {      throwIfCancellationRequested(config);      // Transform response data      if (reason && reason.response) {        reason.response.data = transformData(          reason.response.data,          reason.response.headers,          config.transformResponse        );      }    }    return Promise.reject(reason);  });};

throwIfCancellationRequested是如果已经取消请求则抛出错误

dispatchRequest使用配置的adapter向服务端发起请求,里面分几个步骤:

  1. 检测到如果请求已经取消则抛出错误
  2. 确保请求头存在
  3. 传入数据和请求头,根据数据类型转换请求数据
  4. 抽出请求头的common和当前请求的methods合并到首层结构
  5. 删除header属性里无用的属性
  6. 确认adapter
  7. 执行adapter(config)返回Promise然后执行对应操作

    • 成功

      • 检测到如果请求已经取消则抛出错误
      • 否则处理返回数据格式
    • 失败

      • 如果!isCancel(reason)成立,检测到如果请求已经取消则抛出错误
      • 否则直接返回数据

axios/lib/core/transformData.js

负责解析请求/响应数据,只是单纯的使用utils.forEach而已

var utils = require('./../utils');/** * Transform the data for a request or a response * * @param {Object|String} data The data to be transformed * @param {Array} headers The headers for the request or response * @param {Array|Function} fns A single function or Array of functions * @returns {*} The resulting transformed data */module.exports = function transformData(data, headers, fns) {  /*eslint no-param-reassign:0*/  utils.forEach(fns, function transform(fn) {    data = fn(data, headers);  });  return data;};

至此,整个axios库的源码已经讲完了.