在对用户进行身份验证的应用程序中,我调用了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})
}))
}
}发布于 2019-03-01 08:46:03
编辑: @tony19 19的答案 更好,因为它允许在第一个错误之后取消仍在等待的请求,并且不需要任何额外的库.
一种解决方案是为您同时使用的所有请求分配一个唯一标识符(本例中我将使用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})
}))
}
}发布于 2019-03-26 06:45:29
高票答复提出了一种需要等待所有响应完成的解决方案、对uuid的依赖以及拦截器中的一些复杂性。我的解决方案避免了所有这些,并解决了终止Promise.all()执行的目标。
Axios支持请求取消,因此您可以使用一个错误处理程序包装您的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
}
}发布于 2019-04-01 02:30:16
作为Axios的替代方案,您可以使用更简单的蓝鸟承诺取消。
与旧的取消相比,新取消的好处是:
这是一个演示。我在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()
看看这个古老的公理问题,#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
});因此,我们可以直接使用承诺,并且仍然具有相同的功能。
承诺快速失败
从MDN我们看到
如果拒绝任何元素,则拒绝Promise.all。例如,如果您传入四个在超时后解决的承诺和一个立即拒绝的承诺,那么Promise.all将立即拒绝。
所以在这种模式下
Promise.all(...)
.then(...)
.catch(...);当第一个承诺失败时,.catch()将触发(与等待所有承诺完成的then()相反)。
构成Promise.all 和 .cancel()的
模式非常简单,只需取消.catch()中的所有承诺(这是在第一个错误时调用的)。
有关详细信息当Promise.all()拒绝时停止其他承诺,请参阅此问题
在Vue商店中替代蓝鸟
这是Vuex的基本实现。
yarn add bluebirdimport 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());
})
}
}
});https://stackoverflow.com/questions/54777218
复制相似问题