Axios / Vue-阻止axios.all()继续执行

时间:2019-02-20 00:53:32

标签: javascript vue.js axios vue-cli

在我的应用程序中,对用户进行身份验证时,我调用fetchData函数。如果用户令牌无效,则应用程序将运行axios.all(),而我的拦截器将返回很多错误。

如何防止axios.all()在第一个错误发生后继续运行?并只向用户显示一条通知?

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

3 个答案:

答案 0 :(得分:6)

编辑:@tony19's answer更好,因为它可以取消出现第一个错误后仍待处理的请求,并且不需要任何额外的库。


一种解决方案是为您同时使用的所有请求分配一个唯一的标识符(在此示例中,我将使用uuid/v4包,请随时使用其他名称):

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

然后,在拦截器中,您可以选择使用此唯一标识符一次性处理该错误:

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

附加说明,您可以将fetchData函数简化为:

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

答案 1 :(得分:2)

作为Axios取消的替代方法,您可以使用更简单的Bluebird Promise Cancellation

  

与旧取消相比,新取消的优点是:

     
      
  • .cancel()是同步的。
  •   
  • 无需设置代码即可使取消生效
  •   
  • 与其他蓝鸟功能(例如 Promise.all
  • )组成   

这里是一个演示。我在axios.get(...).then(...)中添加了一些日志记录,以跟踪每个呼叫是否完成。

注释行promises.forEach(p => p.cancel()),以验证在不取消的情况下无错误的调用将完成。

//for demo, check if fetch completes 
const logCompleted = (res) => console.log(`Promise completed, '${res.config.url}'`) 

function getChannels() {
  return axios.get("https://reqres.in/api/users?page=1&delay=5").then(logCompleted)
}
function getContacts() {
  return axios.get("https://reqres.in/api/users?page=2").then(logCompleted)
}
function getEventActions() {
  return axios.get("https://httpbin.org/status/401").then(logCompleted)
}

Promise.config({ cancellation: true }); // Bluebird config
window.Promise = Promise; // axios promises are now Bluebird flavor

const promises = [getChannels(), getContacts(), getEventActions()];
Promise.all(promises)
  .then(([channels, contacts, eventActions]) => {
    console.log('Promise.all.then', { channels, contacts, eventActions });
  })
  .catch(err => {
    console.log(`Promise.all.catch, '${err.message}'`)
    promises.forEach(p => p.cancel());
  })
  .finally(() => console.log('Promise.all.finally'))
<script src="https://cdnjs.cloudflare.com/ajax/libs/axios/0.18.0/axios.min.js"></script>
<script src="https://cdn.jsdelivr.net/bluebird/latest/bluebird.core.min.js"></script>


为什么起作用

Promise.all()而不是axios.all()

看看这个古老的axios问题Remove axios.all and axios.spread #1042可以看到

  

Axios在幕后使用Promise.all ...

还有这个

axios.all([getUserAccount(), getUserPermissions()])
  .then(axios.spread(function (acct, perms) {
    // Both requests are now complete
}));

可以替换为此

Promise.all([getUserAccount(), getUserPermissions()])
  .then(function ([acct, perms]) {
    // Both requests are now complete
});

因此我们可以直接使用Promises,但仍具有相同的功能。


承诺很快失败

MDN中我们看到

  

Promise.all如果任何元素被拒绝,则被拒绝。例如,如果您传入四个在超时后解决的promise和一个立即被拒绝的promise,则Promise.all将立即被拒绝。

在这种模式下

Promise.all(...)
.then(...)
.catch(...);

.catch()将在第一个承诺失败时触发(与then()相比,它会等到所有承诺都完成)。


组成Promise.all.cancel()

模式非常简单,只需取消.catch()中的所有promise(在第一个错误时调用)。

有关详细信息,请参考此问题Stop other promises when Promise.all() rejects


在Vue商店中替代Bluebird

这是Vuex中的基本实现。

yarn add bluebird
import Vue from "vue";
import Vuex from "vuex";
import axios from "axios";
import Promise from 'bluebird';
Vue.use(Vuex);

Promise.config({ cancellation: true }); // Bluebird config
window.Promise = Promise; // axios promises are now Bluebird flavor

export default new Vuex.Store({
  actions: {
    fetchData({ dispatch }) {
      function getChannels() {
        return axios.get("https://reqres.in/api/users?page=1&delay=5");
      }
      function getContacts() {
        return axios.get("https://reqres.in/api/users?page=2");
      }
      function getEventActions() {  // 401 - auth error
        return axios.get("https://httpbin.org/status/401");
      }

      const promises = [getChannels(), getContacts(), getEventActions()];
      Promise.all(promises)
        .then(([channels, contacts, eventActions]) => {
          dispatch("channels/setChannels", channels.data, { root: true });
          dispatch("contacts/setContacts", contacts.data, { root: true });
          dispatch("events/setActions", eventActions.data, { root: true });
        })
        .catch(err => {
          promises.forEach(p => p.cancel());
        })
    }
  }
});

答案 2 :(得分:1)

upvoted answer提出了一个解决方案,该解决方案需要等待 all 所有响应完成,对uuid的依赖以及拦截器的某些复杂性。我的解决方案避免了所有这些情况,并实现了终止Promise.all()执行的目标。

Axios支持request cancelation,因此您可以使用错误处理程序包装GET请求,该处理程序可以立即取消其他未决请求:

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
  }

  ...
}

在拦截器中,您还将忽略请求取消带来的错误:

export default (http, store, router) => {
  http.interceptors.response.use(
    response => response,
    error => {
      if (http.isCancel(error)) {
        return Promise.reject(error)
      }

      ...

      // show notification here
    }
}

demo