I have a service.js which has some axios requests. When I post something using this service from view, I can't handle errors, because all errors falls into success chain instead of error.
This line is from service.js
async createIsp(payload) {
return await apiService.post('/isp', payload)
}
And this one is from view:
function saveIsp() {
clicked.value = true
ApiService.createIsp({isp_name: newIsp.value}).then((data) => {
clicked.value = false
//all response falls here... even its error.
//router.push({name: 'isp'})
}).catch(e => console.log(e))
https://stackoverflow.com/a/70542347/3054818
This is the solution. I am using interceptors, and I should throw error.
Related
I'm new to vue. I use interceptors for handling action responses, all easy with successful responses. But I would like to know what are the best practice to handle error responses.
I want to show a toastr with error message from response by default if there's no catch block in the action, but if there is a catch, do only catch function with no toastr shown.
Also, is it ok to handle unauthorized response making a redirect to login page directly in interceptor and what advices can be given about it?
My current interceptor looks like this:
axios.interceptors.response.use(
(response) => {
return response.data.data;
},
(error: AxiosError) => {
const data = error.response?.data;
const code = data?.code;
if (code === ErrorCodes.NEED_EMAIL_CONFIRMATION) {
router.push("email-verification").then();
} else if (code === ErrorCodes.UNAUTHORIZED) {
router.push("sign-in").then();
} else {
if (undefined !== data.error) {
toaster.error(data.error);
} else {
toaster.error(i18n.t("unexpected"));
}
}
return error;
}
);
but I don't like too many responsibilities here and I don't know how to avoid toastr show when the action has a catch function
You can control error toast notification from where you send the request, by sending an extra config.
Using axios:
axios.post('/api-name', data, {
config: {
showToast: true,
},
})
and then on axios intercept:
axios.interceptors.response.use(
response => {...},
error => {
const showTost= error.config.errorToast
if(showToast){
// show toast you can pass custom message too...<3
}
}
I'd like to catch the error in component level and prevent propagation while using the useQuery in #apollo/react-hook.
Here is my example code
const invitationDocument = gql`
query DecodeInvitation($token: String!) {
DecodeInvitation(token: $token) {
name
email
}
}
`
const InvitationPage = (props) => {
const { data, error, loading } = useQuery(invitationDocument, {variables: { token: "XXXX" }});
if(error)
{
return <InvitationErrorPage error={error.message}/>
}
return loading? <LoadingPage> : <InvitationAcceptPage />
}
It works fine but at the same time, the error is being propagated to its parents level so I get another error notification message which comes from the error handler at the global level.
At the application level, I use the apollo-link-error to manage the Graphql errors.
import { onError } from 'apollo-link-error';
const errorLink = onError (({ graphqlErrors, networkError }) => {
if(graphqlErrors)
notification.error(graphqlErrors[0].message);
});
const client = ApolloClient({
cache: new InMemoryCache(),
link: ApolloLink.from([
errorLink,
new HttpLink({ uri: `http://localhost:8080/graphql`})
])
})
For now, I am finding a solution to stop propagation to top-level so that I can show only InvitationErrorPage and stop displaying error notification at the global level.
I was also trying to prevent errors from being logged in an Error Link by handling them on a useQuery hook and a further delve into the ApolloLink documentation helped clear up what is happening. The key misunderstanding is that the Error Link is not a parent- or application-level handler, it is request middleware. It's helpful to think about how the data is coming back from the server:
Thus, when you see an error notification from the Error Link it is not something that "propagated up" from the useQuery hook: it occurred in the request path before the useQuery result was available on the client.
Thus, the onError callback for the Error Link will always be called before any error handling code in the useQuery hook.
Probably your best bet is to use a combination of the operation and graphQLErrors[x].extensions to figure out what errors you should pass through the Error Link middleware like so:
const errorLink = onError(({operation, response, graphQLErrors}) => {
if (!graphQLErrors) {
return;
}
if (operation.operationName === "DecodeInvitation") {
for (const err of graphQLErrors) {
if (err.extensions?.code === 'UNAUTHENTICATED') {
// Return without "notifying"
return;
}
}
}
// Notify otherwise
notification.error(graphqlErrors[0].message);
})
I have a problem that I can't solve with vue.js
I intercept queries that return an error (axios interceptor), and when it passes through this interceptor, the catch of the axios query is still taken into account.
Except that I wait for an error "error.api". which I don't receive, so it generates a console error.
Here is the code:
axios.interceptors.response.use(null, error => {
let path = '/login';
switch (error.response.status) {
case 401: path = '/login'; break;
case 404: path = '/404'; break;
}
store.commit('logout')
router.push(path);
return Promise.reject(error);
});
this error
2.js:376 Uncaught (in promise) TypeError: Cannot read property 'api' of undefined
And finally, the axios query and the error is generated by the last line (err.api[0])
deleteApi(id) {
this.$store.dispatch('deleteApi', id)
.then((res) => {
this.$toast.success(res.data.success)
this.deleteApiModal(id)
})
.catch(err => this.$toast.error(err.api[0]))
},
I finally found a solution but very dirty, which I find repetitive for not much.
It's on each call axios, to put a condition that checks if "err" exists...
I would have preferred to be able to interact on the interceptor to have managed this in only one place.
If someone has a better solution, I'll take it !
deleteApi(id) {
this.$store.dispatch('deleteApi', id)
.then((res) => {
this.$toast.success(res.data.success)
this.deleteApiModal(id)
})
.catch(err => { if(err) this.$toast.error(err.api[0]) })
},
I'd like to intercept all api responses with the code != 200 in my main.js with the following code, dispatch an action and after that, show a toast showing the error message. I'm using vue-resource and my interceptor is the following:
Vue.http.interceptors.push(function(request, next) {
next(function(response) {
debugger;
if (response.status != 200) {
store.dispatch("errorAction", response);
}
});
});
But the code inside the callback is never reached...
And my api call is done this way. The java controller just throws an exception with the 500 error code.
Vue.http
.get(`http://${ADDRESS}/${store.state.module}/foo/exception`)
.then(() => {}, () => {});
I'm new with Promises and probably i'm messing things, but i don't want to be passing an error callback to every single promise. And what if my request is as follows:
export function getFoo(cb) {
Vue.http
.get(`http://${ADDRESS}/${store.state.module}/foo`)
.then(
response => {
return response.json();
},
() => {}
)
.then(foos => {
cb(foos);
});
}
I would like to get rid off () => {} and use the interceptor code to be run.
I think I'd had the same issue, so I tried to look at the documentation. Well, it is very simple, you just need to do something like this:
In main.js
Vue.http.interceptors.push(function(req) {
//Here you can add some headers, if needed
req.headers.set('awesomeHeader', 'owwnt')
return function(res) {
if( res.status == 200 || res.status == 201 || res.status == 202 ){ //Here you add the status codes that you'll work with, just like my example
//Sucess response
} else {
//Every time witch an request return a status differ form the list above, you can do whatever you want, for example you can redirect the page for a new one
window.location.href = `http://localhost:8080/#`
//If you want to display a notification, you need to import the component before, and then do something like it:
Notification.success({
title: 'error',
message: 'Unauthorized request!',
offset: 100
})
}
};
})
Does it answer you question? I hope it does.
here is what i do, and i'am not realy sure its correct :
//store
async addUser({commit}) {
try {
const {data} = await apiService.addUser()
commit('SET_USER', data)
commit('SET_NOTIFICATION', {type:'success', message: 'user successfuly created'})
} catch (error) {
commit('SET_NOTIFICATION', {type:'error', message:error})
}
}
SET_USER(state, user) {
state.users.push(user)
}
//my component:
async addUser() {
this.isLoading = true
await this.$store.dispatch('updatePatient', this.form)
this.isLoading = false
}
is it legit ?
sometimes i think i would need more logic inside my component depending on the succes or rejected api request. Should i put all the logic in my actions ? like i do at the moment ?
Maybe should I add a status state for each actions, for example :
state {
users: []
postUserSuccess: null
postUserError: false
updateUserSuccess: null
updateUserError: false
// ...
}
and do what i want in the component with a computed property mapped to the store ?
What do you think ?
I don't know if it's a best practice but I let the components the exception handling. That method has its pros (you don't have to pollute the state with error management) and cons (you have to repeat the error management code for every action call).
All service calls will be made in actions
The state will only be set in mutations.
All service calls will return a promise with a resolve(data to load in the state) and a reject(message errors to present).
There will be an interceptor to reject the response in case there's a custom error (here you can put if the response has an error prop reject the response and send as an error the error prop, now you don't have to deconstruct the response in the action).
I'm going to give you a simplified example (I use axios, you can learn how to do it with the library that you use).
Actions in Vuex are asynchronous. So you don't need to try/catch them.
ApiService - Add User
const addUser = () => {
return new Promise((resolve, reject) => {
axios
.post(url, user)
.then(response => resolve(response.data))
.catch(error => reject(error));
});
};
store
async addUser({commit}) {
const data = await apiService.addUser();
commit('SET_USER', data);
return data;
}
if the promise in apiService.addUser is resolved the commit is going to be made if is rejected axios will return the promise and you can catch the error in the component that calls the action.
Component
async addUser() {
this.isLoading = true;
try {
await this.$store.dispatch('updatePatient', this.form);
} catch (error) {
// here goes the code to display the error or do x if there is an error,
// sometimes I store an errors array in the data of the component other times I do x logic
}
this.isLoading = false;
}
State
Your state will be cleaner now that you don't need to store those errors there.
state {
users: []
}