Black-Hole's Blog

In love

分析axios源码来找出无法使用all和spread等方法的原因

Posted at — Apr 14, 2018

前言

如果你在使用axios的时候,是使用axios.create({})方法来进行创建axios的,那么你会发现你无法使用allspreadCancelCancelTokenisCancel方法。

我上网查了相关的问题,axios维护者们都是让你重新引入axios package来进行完成任务。我不喜欢这种方法,因为重新引入的话,那我的axios配置就会丢失,需要重新配置一遍,太过麻烦。

因为我们项目很多时候,不想使用默认的配置,想使用自定义设置的axios实例。比如设置基础URL和超时时间:

let newAxios = axios.create({
  baseURL: 'https://www.google.com.hk',
  timeout: 1000
})

设置完后,使用newAxios.post来完成自己的需求,当然,如果你只使用getpostput等基础的方法,是没有问题的。但是如果你使用allspreadCancelCancelTokenisCancel方法,将会告诉你,方法不存在。

现在,让我们看一下axios源码是如何实现的,为什么使用axios.create方法后,就无法使用allspread等方法。

正文

我们先打开axios源码目录下的lib/axios.js文件。这个文件就是Axios入口处。也是create函数所在的地方。我们现在来看看create的源代码:

axios.create = function create(instanceConfig) {
  return createInstance(mergeConfig(axios.defaults, instanceConfig));
};

我们先逐步解读。看到mergeConfig方法,大家就能从字面上理解了。这是一个合并配置的方法。就是把我们配置与默认配置进行合并,把我们的配置覆盖默认的配置。合并配置的代码,这里就不细说了,有兴趣的可以去mergeConfig看下。所以现在的代码变成了这样:

axios.create = function create(instanceConfig) {
  return createInstance({
    baseURL: 'https://www.google.com.hk',
    timeout: 1000,
    xsrfCookieName: 'XSRF-TOKEN',
    xsrfHeaderName: 'X-XSRF-TOKEN',
    maxContentLength: -1,
    /* 等等 */
  });
};

现在看是不是就有点清晰了。现在我们看到还剩一个函数createInstance,现在让我们去看一下:

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

context变量内容是axios实例代码。我们来看下,里面大致长啥样:

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

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

不用细看,我们只需要知道,实例Axios后,context变量里的原型链上有request delete get head options post put patch方法,自身有request interceptors对象。

现在,让我们看下下面的bindextend方法:

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

第一个bind函数,是让Axios.prototype.request函数里的this指向context变量。

后面两个extend方法,是把第二参数的可枚举对象复制到第一个参数中,也就是instance变量里。

我们从第一个bind方法开始,现在instance变量里有一个request方法。

然后第二个extend方法,把Axios.prototype里的方法复制到instance变量里。现在instance变量里有request delete get head options post put patch方法。

最后第三个extend方法,把context里的方法复制到instance变量里。现在变量里有request delete get head options post put patch interceptors defaults

然后就没了,create方法直接返回instance变量。是不是根本没有看到allspread等方法。这也就是为什么使用create方法后,无法使用这些方法。那么这些方法在哪呢?还是在lib/axios.js文件里:

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

module.exports = axios;

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

可以看到,这里是把这些方法直接赋值在axios方法上,然后就直接暴露出去了。所以当我们使用axios可以使用allspread等方法。但是使用axios.create就无法使用allspreadCancelCancelTokenisCancel方法。

解决方案

如果能改axios源码的话,那可以把lib/axios.js改成如下,就行了:

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

  utils.extend(instance, {
    Cancel: require('./cancel/Cancel'),
    CancelToken: require('./cancel/CancelToken'),
    isCancel: require('./cancel/isCancel'),
    all: function all(promises) {
      return Promise.all(promises);
    },
    spread: require('./helpers/spread')
  }, context);

  return instance;
}

但是,这当然不可能啦。所以,我们需要在不改源代码的情况下,去实现。

有一个暴力的解决方案,不过我挺喜欢的:

let axios = require('axios');

const http = axios.create({
  baseURL: 'https://www.google.com.hk'
})

/* eslint-disable no-proto */
http.__proto__ = axios
/* eslint-enable */

module.exports = axios

是不是很简单,一行代码解决问题。这里之所以要加上注释,因为在eslint里是不允许对__proto__进行重新赋值的。

comments powered by Disqus