Axios 源码分析

808 阅读3分钟

说明

axios 是基于 promise 的http库,可以用在浏览器和node.js 中来处理网络请求。

axios的特点有

  • 从浏览器中创建 XMLHttpRequests
  • 从 node.js 创建 http 请求
  • 支持 Promise API
  • 拦截请求和响应
  • 转换请求数据和响应数据
  • 取消请求
  • 自动转换 JSON 数据
  • 客户端支持防御 [XSRF]

在一些通用配置上,可以指定默认配置

axios.defaults.baseURL = 'https://api.example.com';
axios.defaults.headers.common['Authorization'] = AUTH_TOKEN;
axios.defaults.headers.post['Content-Type'] = 'application/x-www-form-urlencoded';

axios.js 入口文件

axios是源码中已经创建好的一个实例,用 axios.js 中的createInstance函数来创建。此外axios实例上挂载了许多方法

  • 当有许多基于不同配置的请求时,可以利用 axios.create 创建一个新的 axios实例。

    // 创建实例时设置配置的默认值
    var instance = axios.create({
      baseURL: 'https://api.example.com'
    });
    
    // 在实例已创建后修改默认值
    instance.defaults.headers.common['Authorization'] = AUTH_TOKEN;
    

createInstance 函数:

  • new 了一个 Axios 实例 context
  • 将 Axios.prototype.request 的 this 指向 context
  • 将Axios.prototype 中的方法赋给 instance,并将 instance 的this指向 context
  • 将 context 上的方法 赋给 instance
function createInstance(defaultConfig) {
  var context = new Axios(defaultConfig);
  var instance = bind(Axios.prototype.request, context);
  // module.exports = function bind(fn, thisArg) {
  //   return function wrap() {
  //     var args = new Array(arguments.length);
  //     for (var i = 0; i < args.length; i++) {
  //       args[i] = arguments[i];
  //     }
  //     return fn.apply(thisArg, args);
  //   };
  // };

  // Copy axios.prototype to instance
  utils.extend(instance, Axios.prototype, context);
  // function extend(a, b, thisArg) {
  //   forEach(b, function assignValue(val, key) {
  //     if (thisArg && typeof val === 'function') {
  //       a[key] = bind(val, thisArg);
  //     } else {
  //       a[key] = val;
  //     }
  //   });
  //   return a;
  // }

  // Copy context to instance
  utils.extend(instance, context);

  return instance;
}

axios.all 其实就是 Promise.all

axios.all = function all(promises) {
  return Promise.all(promises);
};

Axios.js

Axios.js 中的 axios.prototype.request 是axios 的核心函数。

  • 首先将 传入的配置参数与默认的配置 合并
  • 然后设置 config 的 method并将method转换成小写字母
  • 创建了一个 chain并传入 两个参数:dispathRequest 和 undefined
  • 将config用 promise包装
  • 将请求拦截器中注册的 中间件 从chain的头部一次插入两个,将响应拦截器中注册的中间件从chain的尾部一次插入两个
  • 然后一次从 chain中取出两个参数,先执行 request请求,然后到 dispathchRequest, 再执行 response 响应
  • 最后返回 promise
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;
};
  • 拦截器是一个数组

    function Axios(instanceConfig) {
      this.defaults = instanceConfig;
      this.interceptors = {
        request: new InterceptorManager(),
        response: new InterceptorManager()
      };
    }
    
    function InterceptorManager() {
      this.handlers = [];
    }
    
  • use就是将参数push进拦截器数组,一次必须传入两个参数

InterceptorManager.prototype.use = function use(fulfilled, rejected) {
  this.handlers.push({
    fulfilled: fulfilled,
    rejected: rejected
  });
  return this.handlers.length - 1;
};

dispatchRequest.js

dispatchRequest 是 Axios.prototype.request 调用的处理 axios 发起请求的参数:

  • 首先判断请求是否取消

  • 如果没有取消,用 transformData 将请求数据进行转换

  • 利用utils.merge 将 config.headers.common || {}, config.headers[config.method] || {},config.headers 进行合并

  • 删除多余的请求头

  • 初始化一个适配器,如果当前环境是浏览器就是用 xhr 适配器,如果是node环境,就使用http适配器。

    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;
    }
    
  • 执行适配器,返回用promise包装的 response,将 response 数据转换成如下格式

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