Promise based HTTP client for the browser and node.js
axios is one of the most famous Javascript request libraries. According to Github's data, it is used by 2.1 millions repositories now (Feb 2020).
Purposes of this tutorial are,
- Clarifying misleading behaviors and usages
- Introducing design principles and internal implementations
- Helping users be able to solve problems by themselves
- Avoiding invalid or weak issues or pull requests opened
- Recording personal study notes
- Practicing English writing skills
I'd like to divide things into 3 questions,
- Design Theories, why does axios look like this?
- Usage Knowledges, what didn't mention clearly in axios official document?
- Problem Solutions, how to analyze a problem and open a good issue or pull request?
The most impressive design in axios is its flexible architecture, including basic configs, interceptors, transformers and adapters. The core is simple and stable, while users can achieve customized functionalities by providing their own implementations. Before requesting for new features, think twice whether it is important and common enough to be added, or it can be solved by current hooks.
Make sure you are familiar with asynchronous programming when using axios, especially for Promise A+ and async/await. Because axios connects internal things by Promise.
Let's follow the structure of official document. In each topic, I will give some examples to explain the misleading or unclear points. To keep this document not out-of-date, the detailed logic will not be introduced too much. It may change between different versions, please read specific axios source codes.
Quick links,
Using terms in object-oriented programming, Axios
is the class which provides core request
method and other method aliases, and axios
is an instance of Axios
created by axios.create
with the default configs.
Before returning, axios.create
will bind the instance to Axios.prototype.request
, so axios
is also a function same with Axios.prototype.request
.
Something special is that, axios
has lots of static members and methods besides of axios.create
, i.e. axios.Cancel/CancelToken/isCancel
and axios.all/spread
.
axios.Cancel // object
axios.create(config).Cancel // undefined
For beginners, it may be a little confused. In fact, you can remember easily by asking yourself whether it has a data
parameter.
axios.request([url, ]config) // first or second
axios.delete/get/head/options(url, config) // second
axios.post/put/patch(url, data, config) // third
The above diagram shows all request configs.
- The left dotted box contains 4 main configs(
method
,url
,headers
,data
), which are corresponding to 4 parts in HTTP request format, and their related things. - The right dotted box is
adapter
related. A separate line divides configs into browser only(xhr.js
) and server only(http.js
). Others configs should be applicable to both sides, but are not fully supported. - The rest is configs that control the process, including cancellation, timeout and response transforming.
- Should
method
be lower cases or upper? - Understand how
baseURL
concats withurl
. - Nightmares of
headers
: CORS, cookies andauth
. - Distinguish
params
withdata
. - Serialize
params
correctly. - Submit
data
successfully. - Receive special types of response by
responseType
andresponseEncoding
. - Make things precisely,
transformRequest
andtransformResponse
. - Why was't
timeout
fired at the right time? - How to track progress by
onUploadProgress
andonDownloadProgress
? - Do you use the right
adapter
? - Server side only: transports, agents, proxies and redirections.
According to HTTP specifications, the method field must be all upper cases. The default 2 adapters have done that internally. So axios users can use case-insensitive method
.
I used to worry about headers merging. But in fact, axios will convert received method
to lower cases and keep cases unchanged until sending out.
Don't think it as simple as baseURL + url
. A bad example is,
axios({
baseURL: 'http://a.com/b?c=d',
url: '&e=f'
})
First of all, headers
in axios are request headers, not response headers. Therefore, CORS related problems can't be resolved by adding values in headers
. Considering many users are confused with CORS, I'd like to give some tips about it.
- CORS problems are browser only, when your site requests a resource that has a different origin (domain, protocol, or port) from its own. Node.js scripts and Postman don't have this kind of trouble.
- Sometimes, take it easy for those additional OPTIONS requests. They are preflighted requests, which are very normal things and not strange bugs caused by axios.
- If some headers couldn't be accessed in your codes, even though they were visible in the network panel, please make sure the server responses correct Access-Control-Expose-Headers header.
- As MDN says, when responding to a credentialed request, the server must specify an origin in the value of the
Access-Control-Allow-Origin header
, instead of specifying the "*" wildcard. Or cookies will not be sent, even thoughwithCredentials
has been set true in axios.
In browser environments, CORS-preflight requests are initiated and entirely managed by the user agent; the preflight operations are completely opaque to our userland code and axios has no ability to add additional headers to the outgoing OPTIONS request (because it isn't making it). - #3464, @knksmith57
Some users complain cookies can't be set when the server has responded Set-Cookie
header. You may check whether they are HttpOnly or Secure, and scopes of cookies.
I prefer to set Authorization
manually in headers
to authorize, unless you know exactly what happens in axios. Here are some warnings for users,
- If no
auth
was set, http adapter will try to extract user and password from the url, while xhr adapter does nothing. - And xhr adapter may not be able to handle special characters well.
Merging of headers will be introduced in Config Defaults section.
When you want to send request data, read the endpoint document carefully to make sure where it should be.
- If should be seen in the url, it is
params
, otherwise isdata
. - If the method is
get
, it isparams
with 99% possibilities. Theoretically,get
can also send withdata
, but is very rare. - If the method is
post
, it isdata
with 80% possibilities.post
usually works withdata
and less will have both. - For other methods, apply the similar strategy.
axios({
url: 'http://a.com/b?c=d'
})
// is as well as
axios({
url: 'http://a.com/b',
params: {
c: 'd'
}
})
The default serialization can only handle simple params
. If you find out the built url is not as expected, especially when your params
contains arrays or nested objects as values, you may need to set paramsSerializer
.
var qs = require('qs'); // https://www.npmjs.com/package/qs
// url?a%5B0%5D=1&a%5B1%5D=2&b%5Bc%5D=42
axios(url, {
params: {
a: [1, 2],
b: {
c: 42
}
},
paramsSerializer(params) {
// or any other libraries you like
return qs.stringify(params);
}
})
Here must be the most severely afflicted area. Lots of axios issues seek help due to it.
In requests, (such as POST or PUT), the client tells the server what type of data is actually sent.
So, data
must match with the header Content Type. Followings are its common values.
text/plain
Simple text.
application/json
In this case, data
should be JSON format. If data
is an object (not null), the default transformRequest
will set Content-Type to it automatically.
axios(url, {
data: {
a: 42
}
})
// equals to
axios(url, {
data: JSON.stringify({a: 42})
})
application/x-www-form-urlencoded
As the name indicated, data
should be URL/URI encoded. If data
is an instance of URLSearchParams, the default transformRequest
will set Content-Type to it automatically.
Note that it treats numbers as strings, while application/json
is type-sensitive.
var data = new URLSearchParams();
data.append('a', 42)
axios(url, {
data: data
})
// equals to
var qs = require('qs'); // https://www.npmjs.com/package/qs
axios(url, {
data: qs.stringify({a: '42'})
})
If data
is too large, you can set maxContentLength
as a hacked way to allow that when using http adapter ( and maxRedirects
is not zero and without customized transport
). maxContentLength
is designed as limitation of the response content. axios sends it mistakenly as maxBodyLength
to follow-redirects.
make sure that the server is actually sending a response compatible to that format.
Usually, the original response is text strings. responseType
is an option provided by XMLHttpRequest, and axios adapts it properly in http adapter. It is often useful to get different formats data and download files.
text
.json
, the default value in axios. If an empty string is set, XMLHttpRequest assumes it astext
type. But axios does some tricks in the defaulttransformResponse
.arraybuffer
, returns ArrayBuffer (browser side) or Buffer (server side). Of course, it will makeresponseEncoding
useless.
// transmit an image from somewhere in `express` server (https://www.npmjs.com/package/express)
axios.get(imageUrl, {
responseType: 'arraybuffer'
})
.then(function (response) {
// `res` is the callback parameter of `express` handle
res.type('image/jpeg');
res.end(response.data, 'binary');
});
document
(browser only), returns Document or XMLDocument.blob
(browser only), returns Blob. You can save it with any suitable methods.
// show the downloaded image by an `img` tag
axios.get(imageUrl, {
responseType: 'blob'
})
.then(function (response) {
var reader = new FileReader(); // https://developer.mozilla.org/en-US/docs/Web/API/FileReader/FileReader
reader.readAsDataURL(response.data);
reader.onload = function() {
var imageDataUrl = reader.result;
// `imageEl` is the DOM element
imageEl.setAttribute('src', imageDataUrl);
};
});
stream
(server only), extra enum added by axios. It makes the callback parameter of response event asresponse.data
.
axios.get(imageUrl, {
responseType: 'stream'
})
.then(function (response) {
// it implements the Readable Stream interface
response.data.pipe(somewhere);
});
In server side, you can also set responseEncoding
to decode the response Buffer data with given character encoding. But I think it is something overlapped with transformResponse
.
Something else worthy to be mentioned is configuring the right Accept
in headers
. See more in IANA.
axios.get(url, {
responseType: 'blob',
headers: {
Accept: 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet' // .xlsx
}
})
Transformers can be a function or an array. axios provides default transformRequest
and transformResponse
. Read their codes carefully to ensure they are as well as you think, or reset with your own ones. If you want to add features based on them, remember to concat with the default values.
// do extra things after axios
axios(url, {
transformRequest: axios.defaults.transformRequest.concat([yourTransformRequest])
})
// or put yours first
axios(url, {
transformRequest: [yourTransformRequest].concat(axios.defaults.transformRequest)
})
Transformers are executed in pipeline, without strange behaviours like interceptors. And transformers require to be synchronous normal functions.
only applicable for request methods 'PUT', 'POST', 'PATCH' and 'DELETE'
Transformers will always be executed, no matter what kind of method
is and the response is succeeded or failed. axios says transformRequest
is only applicable for some methods, but "applicable" here is something like "suggested but not disabled".
Without transformers we can also achieve features by interceptors. But transformers focuse on request or response data, and are closer to adapters.
axios supports timeout
by underlayer APIs, XMLHttpRequest's timeout event and request.setTimeout in Node.js. You may face browser compatibilities problems or Node.js environments problems.
If you set timeout
to a small value, i.e. 1 or 2, make sure it doesn't conflict with Javascript's event loop.
Now axios doesn't have a convenient way to validate a timeout error, except for finding special patterns in the error message. And timeoutErrorMessage
is browser only yet.
Someone wishes other types of timeout. Looks like got provides them very well.
They are wrappers of XMLHttpRequest events, which depend on native implementations. axios doesn't have much workarounds for them.
- If no event is fired, check related environments, like different browsers or React Native.
- If
event.total = 0
orevent.lengthComputable = false
when downloading, maybe due to noContent-Length
in response headers. - If
event.total
is the total file size immediately when uploading, it often happens in local destination.
For environments like Electron or Jest, both XMLHttpRequest and process exist in the global context. axios may not select the right adapter
as you want.
axios.defaults.adapter // [Function: httpAdapter] or [Function: xhrAdapter]
If you want to set adapter
explicitly, note that axios makes an alias from http adapter to xhr adapter in browser. Therefore, remember to configure your bundler properly. Refer to Webpack's target or resolve.alias.
axios(url, {
adapter: require('axios/lib/adapters/http')
})
If you like more fashion Fetch API, sorry that axios has not supported yet. You have to write one by yourself or search in npm.
Time to test your skills about Node.js' http and https, especially for options of their request
method. Note that axios only supports part of them.
The transport is determined by the url protocol (starting with https
or not). But usually, the native http/https transport is wrapped by follow-redirects, which is an independent open source library that handles redirections, unless you have set maxRedirects
to zero. You can also choose your own transport by transport
in request config.
Browsers handle redirections automatically. axios has no control for that. But fetch provides an option called redirect
.
You may expect to also get a response when non-2XX status code returns. Remind that validateStatus
checks status code first. See Request Config.
Then don't forget to config validateStatus
as you wish.
axios(url, {
validateStatus: function (status) {
return status >= 200;
}
})
For responses with http status 304, axios just returns the empty body as the server returns. It's your responsibility to cache and retrieve the body contents. But sindresorhus/got provides a built-in cache
option.
Final configs come from three parts, the axios default, the instance and the request.
// `axios` only use the axios default as config defaults, see lib/defaults.js
axios(requestConfig)
// `axios.create` merges the axios default with `instanceConfig` as config defaults
var instance = axios.create(instanceConfig)
instance(requestConfig)
The main merging strategy changes among versions,
- <= 0.18, as simple as
Object.assign
, which means values with the same key will be replaced by the later one. No deep merging unless both field values are objects. - 0.19, introduces a new util method called
deepMerge
, which will clone the object when replacing, but with some bugs. Arrays are cloned as objects. Some config fields (i.e.params
) should be supported deep merging but not, and custom default fields are lost totally. - 0.20, uses different merging strategies for different fields,
- always from request config, i.e. url/method/data
- always merge, i.e. validateStatus
- deep merge unless is undefined, i.e. headers/auth/proxy/params and custom fields
- replace unless is undefined, i.e. other axios fields
Why? Personally, inheritable
url
can be replaced bybaseURL
. And axios already has aliases for methods. Mergingdata
will result in hard tracking and is lack of use cases. But sometimes users may prepare authorisations inparams
.
If users need a request factory, a workaround is
const instance = () => axios(config)
.
For headers
, it includes more things. You can set default headers for methods.
// their priorities should be 1 < 2 < 3
var instance = axios.create({
headers: {
// 1. common headers
common: {},
// 2. methods headers
get: {},
// 3. request specified
'Content-Type': 'application/x-www-form-urlencoded'
}
});
// not `get` method, so only has 1 & 3, but without 2
instance.post(config)
The extra design also brings about understanding pressures and shortcomings. Because you can't use a header with the same name with HTTP methods. But I think it should be avoided anyway.
Let's ask a similar question like method
, should headers
be case-sensitive or not? The answer is derived from HTTP specifications, too. The protocol has no requirements for it, which means case-insensitive and everything will be sent exactly as you requested to the server.
After configured, headers
may be modified in many stages. If your headers become what you don't expect, please check and debug them carefully. Suggest to use hyphen connected Pascal case, as axios doesn't handle case-insensitive very well.
- Request and response hooks, i.e. interceptors and
transformRequest
. lib/adapters/xhr.js
,Content-Type
will be removed ifdata
is FormData or undefined, in order to let the browser to set.Authorization
will be generated fromauth
.xsrfHeaderName
will be set with the value of cookiexsrfCookieName
.
lib/adapters/http.js
,User-Agent
will be set if no user agent is provided.Content-Length
will be set to match the request data.Authorization
will be removed becauseauth
is included in the options.Proxy-Authorization
will be set if the proxy contains anauth
.- Response header
Content-Encoding
will be removed if decompressed.
From Design Theories, we can know the position of interceptors. They are the beginning part (request interceptors) and the ending part (response interceptor) of the handlers chain.
axios.interceptors.request.use(requestResolve1, requestReject1);
axios.interceptors.request.use(requestResolve2, requestReject2);
axios.interceptors.response.use(responseResolve1, responseReject1);
axios.interceptors.response.use(responseResolve2, responseReject2);
axios(config).then(thenBlock).catch(catchBlock);
// equals to
Promise.resolve(config)
.then(requestResolve2, requestReject2)
.then(requestResolve1, requestReject1)
.then(dispatchRequest, undefined)
.then(responseResolve1, responseReject1)
.then(responseResolve2, responseReject2)
.then(thenBlock).catch(catchBlock);
Note that,
- The real request is not sent immediately when you call
axios(config)
, becausedispatchRequest
is one ofthen
handlers. Avoid doing synchronous time-consumed tasks after axios calls.
axios(config);
setTimeout(function () {
// do time-consumed tasks in next event loop
});
- As well as
promise.then(onResolve, onReject)
, the rejection handler can't catch errors thrown by the resolve handler of the sameuse
pair. For example,responseReject1
will not be invoked even ifresponseResolve1
thrown something, butresponseReject2
can. - If you want to break the chain and trigger the final catch block, just don't return quietly in any resolve handlers and make sure no more later reject handlers handle it.
axios.interceptors.request.use(function () {
throw new Error('reason');
// or
return Promise.reject(new Error('reason'));
}, requestReject);
- You can pass
async
functions (imaging them as functions return a Promise) as interceptor handlers, as long as the chain is connected correctly.
axios.interceptors.request.use(async function (config) {
await something;
return 42;
});
// equals to
axios.interceptors.request.use(function (config) {
return Promise.resolve(42);
// as well as
return 42;
});
- The returned value of
use
is interceptor identifier, which is used to eject the interceptor, instead of the instance of interceptor.
var id = axios.interceptors.request.use(requestResolve);
axios.interceptors.eject(id);
- Comparing with the
use
sequences, the execution order of request interceptors is reversed. It is not straight-forward and a little strange, but has been there for many years. - Interceptors can't be inherited. You have to save them somewhere and register again.
Nothing special has to be mentioned here. Read the official document is enough. Several kinds of errors may be caught.
AxiosError
, which is thrown by axios initiative and hasisAxiosError: true
as mark.Cancel
, which is caused by cancellation and can be recognized byaxios.isCancel
method.- Other errors that can be thrown from anywhere. Find out the place carefully.
axios.Cancel
is a simple wrapper of string, with a helper method (axios.isCancel
) to recognize it. axios creates instance of it internally, so I don't know the meaning of exposing it.
var cancelInst = new Cancel(message);
axios.isCancel(cancelInst); // true
axios.CancelToken
is a constructor, which is used to create instances as the value of cancelToken
in request config. The principle is a little complex. Talk is cheap, let me show you codes.
var outerCancelFn;
config.cancelToken = new CancelToken(function executor(cancelFn) {
// 1. A Promise will be created in the constructor,
// and its `resolve` callback is saved temporarily
// 2. Wrap a callback `cancelFn` based on `resolve`,
// as the parameter of `executor`, and call `executor` immediately
/**
* In fact, `cancelFn` looks like,
*
* function cancelFn(message) {
* // 1. create an instance of Cancel and save it as a member of CancelToken instance
* // 2. invoke `resolve` with the saved Cancel instance
* }
*/
// Save `cancelFn` in an outer value and call it with error message at any desirable time
// Why can it cancel the request?
// 1. `dispatchRequest` will check the member field of CancelToken, and throw it if found
// 2. adapters will wait the Promise to be resolved, and throw the resolved value
outerCancelFn = cancelFn;
})
axios.CancelToken.source
is a factory method that does similar things.
var source = CancelToken.source();
// source.token, which is used as `config.cancelToken`
// source.cancel, which is `cancelFn` in above example
Relax when you didn't receive the expected response. Some checkpoints and ideas can be,
- Make sure the network and server work well, without problems like CORS/ATS. It can be approved by switching to other request libraries, i.e. jQuery, cURL.
- Make sure the program runs like you designed, especially that Promise callbacks are connected well.
- Make sure you used axios correctly, without misleading ways in this article. You can also search in Google, stackoverflow and axios issues.
- Don't reply to issues with only "Same here" or "+1" (reactions are enough). That doesn't make sense, expecting for telling people "Oh, a new poor guy!". Try to give your NEW information and suggestions.
- Don't reply to closed issues unless they are unsolved without any reasons. Normally maintainers will ignore notifications from closed issues or pull requests.
If all of above answers is yes,
- compare the REAL requested url and headers with required and find out why. "REAL" means reading from browsers' network panel, using softwares (i.e. Charles or Wireshark) to capture packets, or at least debugging in adapters of axios.
- test similar scenarios by underlayer APIs (XMLHttpRequest or http).
- For example,
onUploadProgress
andonDownloadProgress
depend on browsers implementations, which are nearly out of control of axios.
- For example,
Finally, you still determine to shot an issue. OK, as long as keeping in mind how readers will feel when reading your issue and whether they can help.
- Is it reproducible and includes enough information? Follow the issue template is the most basic requirement. Try to give a minimum and runnable code example, instead of lots of personal descriptions. Once you mentioned the real adapter is xhr or http, it will reduce 50% of work to resolve the issue.
- Choices will always be better than questions. Giving possible solutions as far as you can is much more appreciated. Open source needs powers from the community, and readers/maintainers are just normal developers like you, who also likely don't know answers.
Great! Now you are the smartest man/woman in the world, because you find a bug/improvement that nobody realized before. Like other projects, axios provides its Contributing guide. And I want to emphasize some key points, which are also applicable to all open source projects.
- Testing is more important than codes. Nobody will never make mistakes and can always cover every corner case. Solid testings include both positive and negative cases.
- Change as less as possible. Unless resulting in breaking features or introducing bugs, don't touch anywhere else. Sometimes "sweet" enhancements will cause much more maintaining burdens.
- Be patient, persistent, and always keep an eye on it. It may be hard, but still hope contributors treat them as targets. axios is famous but not actively maintained. Revising repeatedly according to review suggestions may take weeks or months. If anyone has time, feel free to leave comments to help review pull requests.
It is a guide for directions, instead of all-powerful encyclopedia.
Why not amend axios official document directly? This personal repository can be updated sooner, and affects axios reputations less. Hope I didn't make serious mistakes and offend anyone. Feedbacks will also be shared with axios team timely.
Welcome everyone to open issue or pull request to give suggestions for this document. But don't ask axios questions here.