读 axios 源码

273 阅读4分钟

axios 的源码相对还是比较简单的,但是中间件的思想还是很巧妙的,读的过程中做了一些中文注释。

在使用 axios 时,都会使用 axios.create 方法去创建一个实例,那么就找到 create 方法,从他看起。首先找到入口文件 lib/axios.js

// 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 the default instance to be exported
var axios = createInstance(defaults)

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

// 使用 create 创建实例
axios.create = function create(instanceConfig) {
  // 使用 createInstance 创建实例时,会将传入的 config 挂载到实例的 defaults 上
  return createInstance(mergeConfig(axios.defaults, instanceConfig))
}

// 扩展 axios
// Expose Cancel & CancelToken
axios.Cancel = require('./cancel/Cancel')
axios.CancelToken = require('./cancel/CancelToken')
axios.isCancel = require('./cancel/isCancel')

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

// 导出 axios
module.exports = axios

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

入口文件导出了 axios ,并且定义了 create 方法。create 方法内部返回了 createInstance 方法的调用,反观上边定义的 createInstance 方法。

// lib/axios.js

/**
 * 创建一个 axios 实例
 *
 * @param {Object} defaultConfig 实例的默认 config
 * @return {Axios} axios 实例
 */
function createInstance(defaultConfig) {
  // 使用 Axios 构造函数生成实例,上边包括了 defaults 和 interceptors 属性
  var context = new Axios(defaultConfig)
  // bind 返回一个方法,使用 context 调用 Axios.prototype.request
  var instance = bind(Axios.prototype.request, context)

  // instance 继承 Axios.prototype 的属性,如果是方法将 this 指向 context
  utils.extend(instance, Axios.prototype, context)

  // instance 继承 context 的属性
  utils.extend(instance, context)

  // 返回 instance,执行时实际执行的是 Axios.prototype.request
  return instance
}

利用 new Axios(defaultConfig) 生成 axios 实例,以下是 Axios 构造函数。

// lib/core/Axios.js

var InterceptorManager = require('./InterceptorManager')

/**
 * Axios 的构造函数
 *
 * @param {Object} instanceConfig 生成实例所需的 config
 */
function Axios(instanceConfig) {
  // 将 instanceConfig 赋值到实例的 defaults 上
  this.defaults = instanceConfig
  // 实例上定义拦截器,分别为 请求拦截器 和 响应拦截器
  this.interceptors = {
    request: new InterceptorManager(),
    response: new InterceptorManager(),
  }
}

module.exports = Axios

然后先来看看 InterceptorManager 是个什么东西。

// lib/core/InterceptorManager.js

var utils = require('./../utils')

function InterceptorManager() {
  // 实例上添加 handlers 数组,方便后续的循环操作
  this.handlers = []
}

/**
 * 添加一个新的拦截器到 handles 中
 *
 * @param {Function} fulfilled The function to handle `then` for a `Promise`
 * @param {Function} rejected The function to handle `reject` for a `Promise`
 *
 * @return {Number} 返回一个 id 用来删除拦截器
 */
InterceptorManager.prototype.use = function use(fulfilled, rejected) {
  this.handlers.push({
    fulfilled: fulfilled,
    rejected: rejected,
  })
  return this.handlers.length - 1
}

/**
 * 从 handles 中删除一个指定的拦截器
 *
 * @param {Number} id use 中返回的 id
 */
InterceptorManager.prototype.eject = function eject(id) {
  if (this.handlers[id]) {
    this.handlers[id] = null
  }
}

/**
 * 迭代拦截器
 *
 * @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

想必 use 方法大家都很熟悉,在定义请求拦截器和响应拦截器时都会用到,作用就是向对应的拦截器实例的 handlers 上 push 成功和失败的回调。然后在调用 Axios.prototype.request 的时候最这些回调进行处理。

// lib/core/Axios.js

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

/**
 * 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
  // 因为 shift 两次,所以用 undefined 占位
  var chain = [dispatchRequest, undefined]
  // 返回 config
  var promise = Promise.resolve(config)

  // 请求拦截器添加到 chain 的前边
  this.interceptors.request.forEach(function unshiftRequestInterceptors(
    interceptor
  ) {
    // 成功和失败依次添加
    chain.unshift(interceptor.fulfilled, interceptor.rejected)
  })

  // 响应拦截器添加到 chain 的后边
  this.interceptors.response.forEach(function pushResponseInterceptors(
    interceptor
  ) {
    // 成功和失败依次添加
    chain.push(interceptor.fulfilled, interceptor.rejected)
  })

  while (chain.length) {
    // 第一次 shift 拿到 fulfilled,第二次 shift 拿到 rejected
    // Promise.resolve(config),第一次的请求拦截器可以拿到 config
    promise = promise.then(chain.shift(), chain.shift())
  }

  return promise
}

这个方法中一开始对 config 进行了一些处理,然后定义了一个 chain 数组,接下来就是中间件的核心代码了。chain 默认值是实际发请求的代码,然后将 config 使用 Promise.resolve 包装成 Promise 对象,接下来遍历 request 这个请求拦截器实例,将成功和失败的回调添加到 chain 的前边;响应拦截器实例同理,只不过要放到 chain 的后边。

这样以来,chain 的长度不为零时,循环执行 then 方法,就会先去执行所有定义好的请求拦截器,因为用到了 unshift 去添加,所以最先定义的会最后执行,然后执行 dispatchRequest ,最后再执行所有定义好的响应拦截器,这就是中间件的整个流程。

再来看看 dispatchRequest 中做了什么,他是怎么做到浏览器和 node 中都可以使用的。

// lib/core/dispatchRequest.js

/**
 * 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) {
  var adapter = config.adapter || defaults.adapter

  // 调用 adapter 返回浏览器或 node 的请求方法
  return adapter(config).then(
    function onAdapterResolution(response) {
      // Transform response data
      response.data = transformData(
        response.data,
        response.headers,
        config.transformResponse
      )

      return response
    },
    function onAdapterRejection(reason) {
      if (!isCancel(reason)) {
        // Transform response data
        if (reason && reason.response) {
          reason.response.data = transformData(
            reason.response.data,
            reason.response.headers,
            config.transformResponse
          )
        }
      }

      return Promise.reject(reason)
    }
  )
}

这个方法主要用到了 adapter ,用户如果不自定义,会用 axios 提供的默认值,方法中对成功和失败的数据进行了处理并返回 Promise ,看样子 adapter 就是发请求的方法了。

// lib/default.js

// 返回浏览器或 node 的请求方式
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
}

// 这个 defaults 就是入口文件导出的 axios 实例化时传入的 config
var defaults = {
  adapter: getDefaultAdapter(),
  ...
}

发请求时会判断当前是什么环境,返回对应的网络请求方法。感兴趣的小伙伴可以自行阅读这两个文件:

以上就是读 axios 源码的全过程。