Interceptors in Axios
How to use request and response Interceptors in Axios to make cool things happen with ease
Published: 26 January 2020
Basics
Consider a simple AJAX call like so:
// index.js
axios
.get("https://reqres.in/api/users/2")
.then(res => console.log(res))
.catch(err => console.log(err))
Now how can we achieve the following:
Enter Interceptors
Interceptors are functions that can modify the data associated with our outgoing HTTP request or incoming response and always run before a request is sent out or a response is let in.
They are of 2 types:
(1) Request Interceptor
(2) Response Interceptor
General structure looks like so:
//Request Interceptor
const requestInterceptor = axios.interceptors.request.use(
configFunction, // has info about our request
errorFunction // handles errors
)
//Response Interceptor
const responseInterceptor = axios.interceptors.response.use(
onSuccessFunction, // has successful response object
onErrorFunction // has any errors
)
How to use them
We can either attach them to our global axios instance (if we have one) or attach them to axios before making a call.
Let's say we would like attach a different token based on the URL being called:
globalAxios.interceptors.request.use(
config =>
config.url.includes("/ai")
? { ...config, headers: { "X-custom": "lalala" } }
: config,
error => Promise.reject({ error: "could not send the request" })
)
globalAxios.get('/intelligence/ai')....
NOTE: Always remember to return the config if you want the request to go through.
If you need completely different Interceptors based on certain requests, its always advisable to create fresh axios instances and assign them the interceptors as needed.
Now an example of response interceptors. Let's says we would like to update the URL if our request has failed the first time around:
axios.interceptors.response.use(
response => response,
error => {
if(error.response.status===404) {
error.config.url = '/this/is/new/url/to/call'
return axios(error.config);
}
return Promise.reject(err);
}
)
globalAxios.get('/intelligence/ai')....
error.config contains our original request and we can directly pass it axios to re-trigger the original request with all the params in place.
But there is an issue with the above request - what if our second url also results in a reject promise with status 404?
In that case, we will be trapped in an infinite loop which will go on forever.
This is where axios custom properties come to our rescue.
Custom Properties in Axios instance
Just like any other in-built properties inside axios instance, we can also define our own custom properties which will then be accessible inside our interceptors to use:
axios({
method: "get", //in-built
url: "some/fancy/url", //in-built
count: 0, //custom
retries: 3, //custom
})
count will be used to keep track of the number of times a call has been made already.
retries will be used to repeat the call that many number of times
Let's see this in action, following from our example above:
//Lets assume we would like to make the call to the same URL 3 times before throwing an error
axios.interceptors.response.use(
response => response,
error => {
const { count, retries } = error.config // extract count and retries
if (error.response.status === 404 && count < retries) {
error.config.count += 1 // update count
return axios(error.config)
}
return Promise.reject(err)
}
)
Accessing Data properties
In some cases, when the request fails, we may want to update the data we originally sent. This can be easily done this with error.config.data, however there is a gotcha.
Consider the following AJAX call which fails since old and new passwords are the same.
axios({
method: "post",
url: "https://reqres.in/api/register",
data: {
email: "xyz@gmail.com",
oldPassword: "old-password",
newPassword: "old-password",
},
count: 0,
retries: 2,
})
.then(res => console.log("success", res))
.catch(err => console.log("new password same as old one"))
And let's say you want to provide a default new password instead of throwing an error. You might try to do the following:
axios.interceptors.response.use(
res => res,
err => {
console.log("error config", error.config)
error.config.data.newPassword = "new-password"
return axios(error.config)
}
)
The update to data object won't happen and no error will be thrown either.
If we check the console, we will find that error.config.data is actually a stringified object, so must use JSON.parse first before accessing/updating any of its properties.
axios.interceptors.response.use(
res => res,
err => {
if (err.config.count < err.config.retries) {
err.config.data = {
...JSON.parse(err.config.data),
newPassword: "new-password",
}
err.config.count += 1
return axios(err.config)
}
return Promise.reject(err)
}
)
Remove an Interceptor
const requestInterceptor = axios.interceptors.request.use(func1, func2)
axios.interceptors.request.eject(requestInterceptor)
Axios Documentation on Interceptors