Analyze the axios source to find out why you can't use all and spread

Intro

If you create axios with axios.create({}), you will find you cannot use all, spread, Cancel, CancelToken, or isCancel.

I looked this up. Axios maintainers tell you to import the axios package again. I don’t like that, because re-importing drops my axios config and I have to set it up again.

We often don’t want the default config. We want a custom axios instance, for example with a base URL and timeout:

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

After that you use newAxios.post. get, post, put and the other basic methods work. But if you use all, spread, Cancel, CancelToken, or isCancel, you will be told the method does not exist.

Let’s look at how axios implements this, and why those methods disappear after axios.create.

Source analysis

Open lib/axios.js in the axios source. This file is the Axios entry point, and where create lives. Here is the create source:

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

Let’s read it step by step. mergeConfig is what it sounds like: it merges our config with the defaults, and ours override the defaults. I won’t go into the merge code. If you’re interested, see mergeConfig (opens in a new tab). So the code is now effectively:

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

That’s a bit clearer. Next is 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 is the axios instance. Roughly:

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

No need to read it in detail. After new Axios, context’s prototype chain has request delete get head options post put patch, and the instance itself has a request interceptors object.

Now look at bind and 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);

The first bind makes this inside Axios.prototype.request point to context.

The two extend calls copy enumerable properties from the second argument onto the first, which is instance.

Starting from bind, instance now has a request method.

The second extend copies methods from Axios.prototype onto instance. Now instance has request delete get head options post put patch.

The third extend copies methods from context onto instance. Now it has request delete get head options post put patch interceptors defaults.

That’s it. create returns instance. There is no all, spread, or the others. That’s why they don’t work after create. Where are they? Still in 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;

These methods are assigned directly onto the axios function, then exported. So with axios you can use all, spread, and the rest. With axios.create you cannot use all, spread, Cancel, CancelToken, or isCancel.

Solution

If you could change axios source, you would change lib/axios.js like this:

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

Of course that’s not going to happen. We need to do it without changing source.

Here’s a blunt solution, which I like:

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

Pretty simple, one line.1

Footnotes

  1. The comments are there because eslint does not allow reassigning __proto__.

Reply to this post on X (opens in a new tab) | View as Markdown