In my application wile authenticating the user I call the fetchData function. If the user token become invalid, the application will run axios.all()
and my interceptor will return a lot of errors.
How to prevent axios.all()
of keep runing after the first error? And show only one notification to the user?
interceptors.js
export default (http, store, router) => {
http.interceptors.response.use(response => response, (error) => {
const {response} = error;
let message = 'Ops. Algo de errado aconteceu...';
if([401].indexOf(response.status) > -1){
localforage.removeItem('token');
router.push({
name: 'login'
});
Vue.notify({
group: 'panel',
type: 'error',
duration: 5000,
text: response.data.message ? response.data.message : message
});
}
return Promise.reject(error);
})
}
auth.js
const actions = {
fetchData({commit, dispatch}) {
function getChannels() {
return http.get('channels')
}
function getContacts() {
return http.get('conversations')
}
function getEventActions() {
return http.get('events/actions')
}
// 20 more functions calls
axios.all([
getChannels(),
getContacts(),
getEventActions()
]).then(axios.spread(function (channels, contacts, eventActions) {
dispatch('channels/setChannels', channels.data, {root: true})
dispatch('contacts/setContacts', contacts.data, {root: true})
dispatch('events/setActions', eventActions.data, {root: true})
}))
}
}
As of July 15, 2020, Axios updated its GitHub README file to reflect that the axios. all helper method has been deprecated and should be replaced with Promise. all .
vue-axios is just a wrapper, exposing axios to components as this. axios , this. $http , or Vue. axios .
When we want to use it in a component in Vue, we have to import the axios package above the config object in the component's script block. If you want to load Axios through a CDN, you can use JsDeliver or unpkg.
js Axios. Vue. js Axios is defined as an HTTP client request for the node and the browser. Axios can be done with simple JavaScript or React and Vue.
EDIT: @tony19's answer is much better as it allows to cancel requests still pending after first error, and does not need any extra library.
One solution would be to assign a unique identifier (I will use the uuid/v4
package in this example, feel free to use something else) to all the requests you use at the same time:
import uuid from 'uuid/v4'
const actions = {
fetchData({commit, dispatch}) {
const config = {
_uuid: uuid()
}
function getChannels() {
return http.get('channels', config)
}
function getContacts() {
return http.get('conversations', config)
}
function getEventActions() {
return http.get('events/actions', config)
}
// 20 more functions calls
axios.all([
getChannels(),
getContacts(),
getEventActions()
]).then(axios.spread(function (channels, contacts, eventActions) {
dispatch('channels/setChannels', channels.data, {root: true})
dispatch('contacts/setContacts', contacts.data, {root: true})
dispatch('events/setActions', eventActions.data, {root: true})
}))
}
}
Then, in your interceptor, you can choose to handle the error a single time using this unique identifier:
export default (http, store, router) => {
// Here, you create a variable that memorize all the uuid that have
// already been handled
const handledErrors = {}
http.interceptors.response.use(response => response, (error) => {
// Here, you check if you have already handled the error
if (error.config._uuid && handledErrors[error.config._uuid]) {
return Promise.reject(error)
}
// If the request contains a uuid, you tell
// the handledErrors variable that you handled
// this particular uuid
if (error.config._uuid) {
handledErrors[error.config._uuid] = true
}
// And then you continue on your normal behavior
const {response} = error;
let message = 'Ops. Algo de errado aconteceu...';
if([401].indexOf(response.status) > -1){
localforage.removeItem('token');
router.push({
name: 'login'
});
Vue.notify({
group: 'panel',
type: 'error',
duration: 5000,
text: response.data.message ? response.data.message : message
});
}
return Promise.reject(error);
})
}
Additional note, you could simplify your fetchData
function to this:
const actions = {
fetchData({commit, dispatch}) {
const config = {
_uuid: uuid()
}
const calls = [
'channels',
'conversations',
'events/actions'
].map(call => http.get(call, config))
// 20 more functions calls
axios.all(calls).then(axios.spread(function (channels, contacts, eventActions) {
dispatch('channels/setChannels', channels.data, {root: true})
dispatch('contacts/setContacts', contacts.data, {root: true})
dispatch('events/setActions', eventActions.data, {root: true})
}))
}
}
The upvoted answer proposes a solution that requires waiting for all responses to complete, a dependency on uuid
, and some complexity in your interceptor. My solution avoids all that and addresses your goal of terminating Promise.all()
execution.
Axios supports request cancelation, so you could wrap your GET
requests with an error handler that cancels the other pending requests immediately:
fetchData({ dispatch }) {
const source = axios.CancelToken.source();
// wrapper for GET requests
function get(url) {
return axios.get(url, {
cancelToken: source.token // watch token for cancellation
}).catch(error => {
if (axios.isCancel(error)) {
console.warn(`canceled ${url}, error: ${error.message}`)
} else {
source.cancel(error.message) // mark cancellation for all token watchers
}
})
}
function getChannels() {
return get('https://reqres.in/api/users?page=1&delay=30'); // delayed 30 secs
}
function getContacts() {
return get('https://reqres.in/api/users?page=2'); // no delay
}
function getEventActions() {
return get('https://httpbin.org/status/401'); // 401 - auth error
}
...
}
In your interceptor, you'd also ignore errors from request cancellations:
export default (http, store, router) => {
http.interceptors.response.use(
response => response,
error => {
if (http.isCancel(error)) {
return Promise.reject(error)
}
...
// show notification here
}
}
demo
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With