Reputation: 4942
Struggling a bit to set up error handling with vuex. There seems to be quite a few ways to do so and little documentation on proper error handling. I've been experimenting with four alternatives, though I haven't found a satisfying solution yet.
Alternative 1 - Catching and processing errors on component
in pages/login.vue:
export default {
methods: {
onLogin() {
this.$store.dispatch('auth/login', {
email: this.email,
password: this.password,
}).then(() => {
this.$router.push('/home');
}).catch((error) {
// handle error in component
});
},
},
}
in store/auth.js:
export const actions = {
login({ commit }, { email, password }) {
return this.$axios.post('/api/login', {
email,
password,
}).then((res) => {
doSomething(res);
});
},
}
PROS
CONS
Alternative 2 - Catching and processing errors in vuex
in pages/login.vue:
export default {
methods: {
onLogin() {
this.$store.dispatch('auth/login', {
email: this.email,
password: this.password,
}).then(() => {
this.$router.push('/home');
});
},
},
}
in store/auth.js:
export const actions = {
login({ commit }, { email, password }) {
return this.$axios.post('/api/login', {
email,
password,
}).then((res) => {
doSomething(res);
}).catch((error) => {
// store error in application state
commit('SET_ERROR', error);
});
},
}
PROS
CONS
Alternative 3 - Catching errors using axios interceptors
in plugins/axios.js:
export default function({ $axios, store }) {
$axios.onError(error => {
store.dispatch('setError', error);
});
}
in pages/login.vue:
export default {
methods: {
onLogin() {
this.$store.dispatch('auth/login', {
email: this.email,
password: this.password,
}).then(() => {
this.$router.push('/home');
});
},
},
}
in store/auth.js:
export const actions = {
login({ commit }, { email, password }) {
return this.$axios.post('/api/login', {
email,
password,
}).then((res) => {
doSomething(res);
});
},
}
PROS
CONS
Alternative 4 - Custom error plugin
I've been experimenting in implementing a custom plugin that catches all exceptions, but I'm not succeeding in making it work.
in plugins/catch.js:
export default (ctx, inject) => {
const catchPlugin = function (func) {
return async function (args) {
try {
await func(args)
} catch (e) {
return console.error(e)
}
}
};
ctx.$catch = catchPlugin;
inject('catch', catchPlugin);
}
in pages/login.vue:
export default {
methods: {
onLogin: this.$catch(async function () {
await this.$store.dispatch('auth/login', { email: this.email, password: this.password });
this.$router.push('/home');
}),
},
}
PROS
CONS
My impression is that there is a lack of documentation on error handling in vue/nuxt. Could anyone get the fourth alternative to work? Would this be ideal? Any other alternatives? What is conventional?
Thank you for your time!
Upvotes: 48
Views: 30218
Reputation: 16368
The reason why option #4 is not working is because you're returning a function that never gets executed:
function catchPlugin(outerFunction) {
return function async innerFunction(args) {
try {
const data = await outerFunction(args);
return { data }
} catch (error) {
return { error }
}
}
}
Usage:
const execute = catchPlugin((args) => {
// do something
})
execute('myArgument');
As you can see you need to execute the inner function as well, to make your example work:
onLogin: this.$catch(async function () {
await this.$store.dispatch('auth/login', { email: this.email, password: this.password });
this.$router.push('/home');
})(), // mind the () :-)
But... I believe handling errors in components is not a bad thing, since this is tightly coupled to your view component. For instance, think about a login component, what we see these days is a global error handler (toastr) which will display a toast message if the username/password is incorrect. From my experience this is not the best behavior, it's a good starting point but better would be to add error messages close to the component displaying what exactly went wrong. Meaning you will always have to add error handling (UI related) in the component itself.
We're also struggling with this in our company with colleagues working on the same product. One is adding error handling, the other one is not.. The only solution, in my opinion, is to educate developers to always add proper error handling. The syntax with async/await
is not that bad:
methods: {
async login (email, password) {
try {
await this.$store.dispatch('auth/login', { email, password })
// do something after login
} catch (error) {
// handle error
}
}
}
One last thing about your con
: Errors not handled and stored in vuex.. Why is this a con? Do you need to have the error globally available? What I see a lot is people putting so much useless state in vuex
that's only used in the component itself. It's not bad to have local component state. Since it's about login, this error should only be known in the login component.
Upvotes: 6
Reputation: 1578
Create an error
key in the state of each Vuex module. Then dispatch the error for a given component to its relative Vuex module. Then create a global handler to watch for errors in the separate Vuex modules and, if one is triggered, display the error.
// store/auth.js
export const state = () => {
return {
success: null,
error: null
}
}
export const actions = {
async login({ commit }, { email, password }) {
try {
const response = await axios.post('/api/login', {
email,
password
})
commit('SET_SUCCESS', response)
} catch(err) {
commit('SET_ERROR', error)
}
}
}
export const mutations = {
SET_SUCCESS(state, payload) {
state.success = payload
},
SET_ERROR(state, payload) {
state.error = payload
}
}
// auth.vue
export default {
methods: {
onLogin() {
try {
await this.$store.dispatch('auth/login', {
email: this.email,
password: this.password
})
if (this.$store.state.auth.success) this.$router.push('/home')
} catch (err) {
console.log(err)
}
}
}
}
// app.vue
export default {
created() {
this.$store.subscribe((mutation, state) => {
if (mutation.type.includes('ERROR')) {
// display error in global error output
console.log(mutation.payload)
}
})
}
}
Upvotes: 1
Reputation: 1349
To address the Con from Alternative 2 you can either
(a) pass in the name of the component or even a reference to the component or
(b) you can persist the error in the state for the component that made the call. Then in your component you could check if there is an error and display it. For that you could use a mixin to forgo the need for boiler plate.,
in store/auth.js:
export const actions = {
login({ commit }, { email, password }) {
return this.$axios.post('/api/login', {
email,
password,
}).then((res) => {
doSomething(res);
commit('save_to_state', { response: res });
}).catch((error) => {
commit('save_to_state', { error });
});
},
}
Upvotes: 1
Reputation: 2619
Use Promise
in action
Example in vuex:
NEW_AUTH({ commit }) {
return new Promise((resolve, reject) => {
this.$axios.$get('/token').then((res) => {
...
resolve();
}).catch((error) => {
reject(error);
})
})
}
In page:
this.$store.dispatch('NEW_AUTH')
.then(() => ... )
.catch((error) => ... )
Upvotes: 3