分析axios源码来找出无法使用all和spread等方法的原因
前言
如果你在使用axios的时候,是使用axios.create({})
方法来进行创建axios
的,那么你会发现你无法使用all
、spread
、Cancel
、CancelToken
、isCancel
方法。
我上网查了相关的问题,axios维护者们都是让你重新引入axios package
来进行完成任务。我不喜欢这种方法,因为重新引入的话,那我的axios配置就会丢失,需要重新配置一遍,太过麻烦。
因为我们项目很多时候,不想使用默认的配置,想使用自定义设置的axios实例。比如设置基础URL和超时时间:
let newAxios = axios.create({
baseURL: 'https://www.google.com.hk',
timeout: 1000
})
设置完后,使用newAxios.post
来完成自己的需求,当然,如果你只使用get
、post
、put
等基础的方法,是没有问题的。但是如果你使用all
、spread
、Cancel
、CancelToken
、isCancel
方法,将会告诉你,方法不存在。
现在,让我们看一下axios源码是如何实现的,为什么使用axios.create
方法后,就无法使用all
、spread
等方法。
正文
我们先打开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
对象。
现在,让我们看下下面的bind
和extend
方法:
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
变量。是不是根本没有看到all
、spread
等方法。这也就是为什么使用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
可以使用all
、spread
等方法。但是使用axios.create
就无法使用all
、spread
、Cancel
、CancelToken
、isCancel
方法。
解决方案
如果能改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__
进行重新赋值的。