axios源码解读

72 阅读9分钟

一位想成为架构师的菜鸟程序猿,我想这句话可以成为我的博客座右铭了;

我们项目中一直使用的axios请求库,竟然我一直不知道他的原理,于是我花了几天时间看了研究下源码同时也看了下其它大佬的博客分析,按照我读代码的思路分析下吧。咱们还是只看作者的主要实现思路

先从入口文件看: 进入到 /lib/axios.js

'use strict';

var utils = require('./utils'); // 工具类库
var bind = require('./helpers/bind'); // 更改函数的this指向  不过es6后已经把bind绑定到了函数的原型上了
var Axios = require('./core/Axios'); // axios的构造函数
var mergeConfig = require('./core/mergeConfig'); // 合并对象的工具函数
var defaults = require('./defaults'); // axios默认的配置项

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

  // Factory for creating new instances
  instance.create = function create(instanceConfig) {
    return createInstance(mergeConfig(defaultConfig, instanceConfig));
  };

  return instance;
}

// Create the default instance to be exported
var axios = createInstance(defaults);

// Expose Axios class to allow class inheritance
axios.Axios = Axios;

// Expose Cancel & CancelToken
axios.Cancel = require('./cancel/Cancel');
axios.CancelToken = require('./cancel/CancelToken');
axios.isCancel = require('./cancel/isCancel');
axios.VERSION = require('./env/data').version;

// Expose all/spread
axios.all = function all(promises) {
  return Promise.all(promises);
};
axios.spread = require('./helpers/spread');

// Expose isAxiosError
axios.isAxiosError = require('./helpers/isAxiosError');

module.exports = axios;

// Allow use of default import syntax in TypeScript
module.exports.default = axios;

createInstance 这个函数是生成是一个函数,并且通过

var instance = bind(Axios.prototype.request, context);

把Axios.prototype.request 这个函数的this指向到 Axios构造函数new出的实例上, 稍后会介绍这个Axios构造函数原型上的request方法就是这个库发起请求的入口函数

utils.extend(instance, Axios.prototype, context);

上面这行代码是是使用了utils里的一个工具extend函数作用是把 Axios原型上的所有的属性和方法都绑定到 instance函数上(绑定完后都归属于instance上的静态方法,这些方法都是直接调用构造函数的方法使用,不能实例后再调用)

instance.create = function create(instanceConfig) {
    return createInstance(mergeConfig(defaultConfig, instanceConfig));
  };

这行代码是往instance函数上继续绑定create方法,这个方法又自调用了 createInstance 这个方法, 可以使用这个create方法自定义配置新建一个 axios 实例 如下图我们axios的文档API 总结下: 这个文件就是暴露出了axios函数;

我们接下来再分析下 Axios构造函数 进入到/lib/core/Axios.js

先看下这个文件代码

'use strict';

var utils = require('./../utils');
var buildURL = require('../helpers/buildURL');
var InterceptorManager = require('./InterceptorManager');
var dispatchRequest = require('./dispatchRequest');
var mergeConfig = require('./mergeConfig');
var validator = require('../helpers/validator');

var validators = validator.validators;
/**
 * 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()
  };
}

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

  var transitional = config.transitional;

  if (transitional !== undefined) {
    validator.assertOptions(transitional, {
      silentJSONParsing: validators.transitional(validators.boolean),
      forcedJSONParsing: validators.transitional(validators.boolean),
      clarifyTimeoutError: validators.transitional(validators.boolean)
    }, false);
  }

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

  while (responseInterceptorChain.length) {
    promise = promise.then(responseInterceptorChain.shift(), responseInterceptorChain.shift());
  }

  return promise;
};

Axios.prototype.getUri = function getUri(config) {
  config = mergeConfig(this.defaults, config);
  return buildURL(config.url, config.params, config.paramsSerializer).replace(/^\?/, '');
};

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

module.exports = Axios;

这个文件核心就是导出一个Axios构造函数

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

这个构造函数中包括了 defaults 配置对象 和 interceptors定义拦截器对象, 另外往原型上又增加了 request(请求函数)

总结下: 这个文件就是暴露出了Axios构造函数;

接下来重点来了 分析下request这个绑定到Axios原型上的方法,这个方法前端往后端发请求的入口

函数,函数体的这一部分咱们不纠结细节, 大致就是整合了一下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';
  }

  var transitional = config.transitional;

  if (transitional !== undefined) {
    validator.assertOptions(transitional, {
      silentJSONParsing: validators.transitional(validators.boolean),
      forcedJSONParsing: validators.transitional(validators.boolean),
      clarifyTimeoutError: validators.transitional(validators.boolean)
    }, false);
  }

在分下剩下的部分 咱们先分下拦截器的构造构造函数,看看里面都做了一些什么操作,进入到 lib/core/InterceptorManager 请求拦截器和响应拦截器都是由这个构造函数实例化来的

'use strict';

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, options) {
  this.handlers.push({
    fulfilled: fulfilled,
    rejected: rejected,
    synchronous: options ? options.synchronous : false,
    runWhen: options ? options.runWhen : null
  });
  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;

先声明了一个InterceptorManager构造函数,定义了一个 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;
};

在原型上定义了一个use方法 那么这个use方法就是我们axios Api中的

这个use方法接收3个参数 第一个是 成功的回调函数、第二个是失败的回调函数、第三个是配置项 use方法把我们在业务层预设的每个拦截器函数都push到handlers数组中保存起来 返回当前的下标值 目的是在取消拦截器方法 eject中能通过下标定位到要取消的拦截器函数

然后再分析 forEach 方法

InterceptorManager.prototype.forEach = function forEach(fn) {
  utils.forEach(this.handlers, function forEachHandler(h) {
    if (h !== null) {
      fn(h);
    }
  });
};

这个方法接收一个函数fn 通过utils.forEach【迭代函数】 循环handler中存储的拦截器对象作为fn的参数并循环执行函数

总结下InterceptorManager文件作用: 声明InterceptorManager构造函数,收集那些外层通过use定义的拦截器, 设置forEach方法

接下来我们再回头看下request方法的后半部分

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

    synchronousRequestInterceptors = synchronousRequestInterceptors && interceptor.synchronous;

    requestInterceptorChain.unshift(interceptor.fulfilled, interceptor.rejected);
  });

把handler存储的请求拦截器 一组一组的拿出来 放到requestInterceptorChain这个数组中

['fulfilled2', 'rejected2', 'fulfilled1', 'rejected1']

可以看出是使用的是unshift方法 后面的拦截器放到数组的前面

var responseInterceptorChain = [];
  this.interceptors.response.forEach(function pushResponseInterceptors(interceptor) {
    responseInterceptorChain.push(interceptor.fulfilled, interceptor.rejected);
  });

把handler存储的响应拦截器,一组一组的拿出来 放到responseInterceptorChain数组中

['fulfilled1', 'rejected1', 'fulfilled2', 'rejected2']

可以看出是使用的是push方法

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

!synchronousRequestInterceptors表示是当为异步的时候 默认就是发起异步请求 再看

var chain = [dispatchRequest, undefined];

dispatchRequest是一个请求函数 后面咱们再细说

Array.prototype.unshift.apply(chain, requestInterceptorChain);
    chain = chain.concat(responseInterceptorChain);

把请求拦截器和响应拦截器分别拼接到chain数组的前后 这样就形成了一个数组链

['请求拦截成功回调', '请求拦截失败回调', dispatchRequest, undefined, '响应拦截器成功回调', '响应拦截器失败回调']

接下来执行while循环 chain数组中以从前往后每次shift取出前两个 并使用then执行函数 此时你应该知道为什么要设置undefined了吧 对就是要分成一组两个两个的 没有任何实际的意义

promise = Promise.resolve(config);

等价于:

promise = function(){
  return new Promise(resolve => {
    resolve(config)
  })
}

接下来咱们重点分析chain数组在promise的下是怎么链式调用的

我们先分别声明:
请求拦截器成功回调:
function reqSuccess(config){
  .....
  return config
}
请求拦截器失败回调:
function reqError(error){
  .....
  return error
}
响应拦截器成功回调
function resSuccess(response){
  xxxxx
}
响应拦截器失败回调
function resError(error){
  xxxxx
}

代码先执行 promise = Promise.resolve(config); 变量promise返回一Promise
while进入第一个循环 拿出 reqSuccess 和 reqError 函数 继续执行 proimse.then(reqSuccess, reqError) 此时reqSuccess中传入的参数是 config 于是请求拦截器在发出请求前可以得到完整的config对象。 因为你没有第一个 promise没有执行reject所有 reqError不执行 。

while进入第二个循环 拿出dispatchRequest和undefined 继续执行 proimse.then(dispatchRequest, undefined) 此时dispatchRequest函数拿到的参数是reqSuccess这个函数返回的值 config 然后dispatchRequest拿到config去执行请求数据任务 undefined不执行操作

while进入第三个循环 拿出resSuccess和resError 继续执行 proimse.then(resSuccess, resError) 此时resSuccess中传入的参数是 dispatchRequest函数请求回来的成功数据 resError函数得到的参数是 dispatchRequest请求失败的数据

这样整个从请求到响应的链式调用就完成了

而且作者后来还为了避免拦截器可能会出现异步情况或有很长的宏任务执行,并且重构之前的代码中,因为请求事放到微任务中执行的,微任务创建的时机在构建promise链之前,如果当执行到请求之前宏任务耗时比较久,或者某个请求拦截器有做异步,会导致真正的ajax请求发送时机会有一定的延迟,所以解决这个问题是很有必要的。

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

  while (responseInterceptorChain.length) {
    promise = promise.then(responseInterceptorChain.shift(), responseInterceptorChain.shift());
  }

  return promise;

其实就是按照代码流同步执行代码

最后再分析下请求函数dispatchRequest 进入到/lib/core/dispatchRequest

'use strict';

var utils = require('./../utils');
var transformData = require('./transformData');
var isCancel = require('../cancel/isCancel');
var defaults = require('../defaults');
var Cancel = require('../cancel/Cancel');

/**
 * Throws a `Cancel` if cancellation has been requested.
 */
function throwIfCancellationRequested(config) {
  if (config.cancelToken) {
    config.cancelToken.throwIfRequested();
  }

  if (config.signal && config.signal.aborted) {
    throw new Cancel('canceled');
  }
}

/**
 * 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.call(
    config,
    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.call(
      config,
      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.call(
          config,
          reason.response.data,
          reason.response.headers,
          config.transformResponse
        );
      }
    }

    return Promise.reject(reason);
  });
};

这个函数的核心是 adapter 这个适配器函数
进入到 /lib/defaults.js

function getDefaultAdapter() {
  var adapter;
  if (typeof XMLHttpRequest !== 'undefined') {
    // For browsers use XHR adapter
    adapter = require('./adapters/xhr');
  } else if (typeof process !== 'undefined' && Object.prototype.toString.call(process) === '[object process]') {
    // For node use HTTP adapter
    adapter = require('./adapters/http');
  }
  return adapter;
}

这个函数是用来区分环境 浏览器环境用xhr(既 XMLHttpRequest) node环境使用原生http发起请求

以上就是axios的整个主线逻辑,写这篇文章也是让我自己理解更加深刻,同时也希望能帮助到你们! 后面我也不断的更新其他源码解析文章!