Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Axios/Vue - Prevent axios.all() to keep executing

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})
        }))
    }
}
like image 958
Caio Kawasaki Avatar asked Feb 20 '19 00:02

Caio Kawasaki


People also ask

Is Axios all deprecated?

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 .

What is the difference between Axios and Vue Axios?

vue-axios is just a wrapper, exposing axios to components as this. axios , this. $http , or Vue. axios .

How do I use Axios in Vue 3?

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.

Does Axios work with Vue?

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.


2 Answers

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})
        }))
    }
}
like image 92
Hammerbot Avatar answered Sep 28 '22 11:09

Hammerbot


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

like image 28
tony19 Avatar answered Sep 28 '22 11:09

tony19