Axios/Vue - предотвращение выполнения axios.all()
В моем приложении, аутентифицирующем пользователя, я вызываю функцию 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})
}))
}
}
Ответы
Ответ 1
РЕДАКТИРОВАТЬ: @tony19 ответ гораздо лучше, поскольку он позволяет отменять запросы, ожидающие обработки после первой ошибки, и не требует дополнительной библиотеки.
Одним из решений было бы назначить уникальный идентификатор (я буду использовать пакет 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})
}))
}
}
Ответ 2
В качестве альтернативы отмене Axios, вы можете использовать Bluebird Promise Cancellation, что проще.
Преимущества новой отмены по сравнению со старой отменой:
- .cancel() является синхронным.
- для отмены работы не требуется установочный код
- сочетаются с другими функциями bluebird, такими как 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>
Ответ 3
Поднятый ответ предлагает решение, которое требует ожидания завершения всех ответов, зависимости от 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
}
}
демонстрация