前言

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

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

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

1
2
3
4
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的源代码:

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

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

1
2
3
4
5
6
7
8
9
10
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,现在让我们去看一下:

1
2
3
4
5
6
7
8
9
10
11
12
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实例代码。我们来看下,里面大致长啥样:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
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方法:

1
2
3
4
5
6
7
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文件里:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
// 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改成如下,就行了:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
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;
}

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

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

1
2
3
4
5
6
7
8
9
10
11
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__进行重新赋值的。

作者信息:

Author: Black-Hole

Blog: http://bugs.cc/

github: https://github.com/BlackHole1/

Twitter: https://twitter.com/Free_BlackHole

Email: 158blackhole@gmail.com

本文地址: http://bugs.cc/2018/04/14/分析axios源码来找出无法使用all和spread等方法的原因/