I am using Vue.js with Vue-Apollo and initiating a User mutation to sign in a user. I am using the graph.cool service.
I have a request pipeline function setup to catch some errors, like an invalid email.
When the request is made with bad / invalid input, my error catch() fires (as expected) and in the network tab I can see the JSON for the custom errors messages. But how do I access these errors / response from within the catch if an error is triggered from graph.cool?
Example:
signin () {
const email = this.email
const password = this.password
this.$apollo.mutate({
mutation: signinMutation,
variables: {
email,
password
}
})
.then((data) => {
// This never fires on an error, so I can't
// show the user the errors in the network repsonse.
console.log(data)
})
.catch((error) => {
// Error in this part fires in the console
// but I'm unable to show the JSON response
// errors because the 'then()' above doesn't execute.
console.error(error)
})
}
I get the following error for an unrecognised user:
Error: GraphQL error: No user found with that information
at new ApolloError (eval at (app.js:956), :34:28)
at eval (eval at (app.js:1353), :139:33)
at
Any idea how to show the errors in the response from within the catch()?
I can literally see the errors I want to show to the user in the response on the network tab here:
...but I can't figure out how to do it.
Any help much appreciated! Thank you.
So, it looks as though I was handling this the wrong way by barking up the wrong tree.
The key to the answer was examining the error from the .catch() with console.dir(error). This revealed some useful keys...namely:
error.graphQLErrors[0]
So all in all, the corrected code looks like this:
signin () {
const email = this.email
const password = this.password
this.$apollo.mutate({
mutation: signinMutation,
variables: {
email,
password
}
})
.then(data => {
console.log(data)
})
.catch(error => {
console.log(graphQLErrorMessages(error))
})
}
The graphQLErrorMessages() function is a helper I wrote, so that I can reuse this in other .catch() blocks:
function graphQLErrorMessages (errorsFromCatch) {
const errors = errorsFromCatch.graphQLErrors[0]
const messages = []
if (errors.hasOwnProperty('functionError')) {
const customErrors = JSON.parse(errors.functionError)
messages.push(...customErrors.errors)
} else {
messages.push(errors.message)
}
return messages
}
It returns an array of error messages (which is what I needed) but you could format this any way you like.
It might be a little https://graph.cool specific in its logic (I'm not so sure), but I hope this ends up helping someone also stuck in a similar situation!
I may be misunderstanding your question so please comment and correct me if I am but it looks like you may be having trouble with Promises more than with Vue or GraphQL.
Just like in a try...catch statement, once you catch an error, your program will continue to execute unless you re-throw the error. For example:
This Catches
try {
codeThatThrowsAnError();
} catch(e) {
// Do Nothing
}
This re-throws
try {
codeThatThrowsAnError();
} catch(e) {
throw new Error("Err 135: I failed")
}
Similarly, in Promise land, you can either catch the error and move like you have in your example, or you can re-throw. What you may be missing is that anything you return from a catch statement will be used in the next then. For example:
somethingReturningAFailedPromise()
.then(doWork)
.catch((err) => {
return "I'm a New Value"
})
.then(console.log)
//=> "I'm a New Value"
It sounds to me like what you need is a data function that is more resilient to failure like the following:
const getUserProfile = (id) => {
return fetchUserData(id)
.catch((err) => {
logError(err);
return {};
})
}
Related
I am using a special wrapper to handle requests error on Frontend.
My error handle (handle.js):
async (handle, customCatch = () => {}) => {
try {
await handle();
} catch (err) {
if (err.response) if (await customCatch(err)) return;
await notify.error('Error !');
}
};
My example use:
handle(async () => {
const users=await axios.get('/users')
})
If the axios requests which in the handle are any error notify will an error alert. It is basic a request. If there aren't any error it will get users information as expected. It prevents duplicate code. Automatically show an error with notify. If we don't use the handle method we would use like this:
try{
const users=await axios.get('/users')
}catch(err){
notify.error('Error !');
}
Okey, I think it is very useful. If we want to handle an error that we know how we would use it ? Other my example:
handle(async () => {
await axios.post('example/login',{password:'test',username:'test'})
}, (err) => {
if (err.response.status === 400) {
this.isWrongEmailOrPassword = true;
return true;
}
})
If it gets 400 error we want to set to isWrongEmailOrPassword variable. So we know that may get 400 error. We can set a custom error handler for custom request error code.
Is correct way this handle method ? How reasonable ? If this way isn't correct way what is correct way ? Thank you.
//LoginScreen.js
import signIn from "amplify-communication"
const LoginScreen = props => {
function _signIn(username, password){
const request = signIn(username, password);
if(request.isCommited)
console.log("User is Signed In");
else
console.log("Error Signing In", request.message);
}
}
// amplify-communication.js
import { Auth } from "aws-amplify";
export async function signIn(_username, _password) {
try {
var user = await Auth.signIn(_username, _password);
} catch (error) {
console.log('Error Signing in');
}
finally {
return {
isCommitted: error ? false : true,
message: error,
payload: user
}
}
}
I have separate file for communicating with Amplify. I am returning an object with different keys.
On running the code it shows me following warnings.
1 - Possible Unhandled Promise Rejection (id: 0):
ReferenceError: error is not defined
ReferenceError: error is not defined
2- Using an insecure random number generator, this should only happen when running in a debugger without support for crypto.getRandomValues
error is only scoped to the catch block, so when you try to use error in the finally block, it's an undeclared identifier. You have the same problem with user on the success path.
Instead, either track that information in variables or repeat the return statement.
export async function signIn(_username, _password) {
try {
const user = await Auth.signIn(_username, _password);
return {
isCommitted: true,
payload: user,
message: undefined, // if you need it, otherwise remove
};
} catch (error) {
console.log('Error Signing in');
return {
iscCommitted: false,
payload: undefined, // if you need it, otherwise remove
message: error,
};
}
}
The thing about the random number generator sounds like it's just a warning from the package you're using about the environment in which you're running the code (e.g., that it doesn't have a strong random number generator).
Side note: It can be correct to turn errors into return values like that, but more times than not it's more correct to allow the error to propagate to the caller (and perhaps to its, caller, etc.) rather than forcing every level to check a return value to see if something worked. Again, without knowing what this function is for, that may or may not apply here, but just flagging it up.
I am building a react-native app, and I am starting to implement a more robust and sophisticated error-handling system, specifically for handling server errors when making http requests. Here is a basic example of how I am currently making http requests in my app.
I have a 'client.js' file which is essentially just a wrapper around axios. I have a 'get' method that looks like this:
const get = async (endpoint, config = {}) => {
try {
const result = await axios.get(domain + endpoint, config);
return result;
} catch (error) {
throw new Error(error.message);
}
};
Then, I have a file for each api endpoint that I need to access. For example, I have a 'posts.js' file, and in that file I have a 'getPosts' method:
const getPosts = async (userID, page, pageSize) => {
try {
const response = await client.get(
`${endpoint}?userID=${userID}&page=${page}&pageSize=${pageSize}`
);
return response.data;
} catch (error) {
throw new Error(error.message);
}
};
And then finally, in the component that is calling getPosts, I have a function that looks something like this:
const loadPosts = async () => {
try {
const response = await getPosts();
// do something with the response from the server
} catch (error) {
// display an error message to the client
}
}
Obviously this is a very simple example of what a request might look like, but this is the basic structure that I use throughout my app. The problem I am having is that it seems very repetitive and messy to have to wrap almost all of my functions in a try/catch block, and then basically raise an error object until I get to the function that is actually going to handle the error. Is there some sort of 'design method' for error handling that simplifies and centralizes this process? Perhaps something similar to an express-middleware when creating a node server? Or is this a standard way to handle errors in javascript?
Thank you to anyone who can help!
As you are using axios as the http library here, so you can take a look at axios interceptor in order to hook the response and do something with that before passing it to the consumer. This will help you to respond to errors raised from once cental place.
axios.interceptors.response.use((response) => {
return response;
}, function(error) {
// do what you want to do with the error.
return Promise.reject(error)
});
Or with ES5 syntax
axios.interceptors.response.use(function (response) {
// Do something with response data
return response;
}, function (error) {
// Not 200 Ok
// Do something with response error
return Promise.reject(error);
});
I’m working on an application where I need to make requests to two apis. I’m using cognito to handle authentication, and then a lambda that communicates with a database. But, I don’t think that my problem is specific to either of those implementations. It could arise with any two apis.
I’m trying to write the process of signing up a new user. I need to create a new user in cognito so that the new user is able to login, and then I need to create a corresponding user in the database that will store the non-authentication related data for the user. If one of the api requests encounters an error, then I need to delete the item that I created in the other api.
My current implementation is basically this:
const signUpNewUser = (userInfo) => {
API.post("user", "/user", userInfo)
.then((response) => {
return COGNITO.post("user", "/user", response.newUserID);
})
.then((res) => {
//BOTH REQUESTS OCCURED WITH NO ERRORS
})
.catch((error) => {
if (error.origin === "COGNITO_ERROR") {
//IF DB CHANGES CONFIRMED BUT COGNITO FAILED, DELETE CREATED GUEST IN DB
return API.delete("guest", "/guest", userInfo);
} else if (error.origin === "DATABASE_ERROR") {
//IF DB CHANGES FAILED THEN COGNITO HAS NOT RUN YET, SO DON'T NEED TO DELETE IN THIS CASE
}
});
};
This follows the pattern I see demonstrated on the internet. However, I’m having trouble distinguishing cognito errors from database errors. In the above code I sort them by error.origin but they don’t actually have a property that reliably indicates their origin. This problem must be common when working with multiple api’s that you don’t control, but I’m having trouble finding a good solution.
It feels I need to nest promises in this situation. I could nest a catch after API.Post and COGNITO.post, and use that catch to throw a new error that has an origin property. Then that would bubble up and get caught by the final catch that handles all errors. Like this:
const signUpNewUser2 = (userInfo) => {
API.post("user", "/user", userInfo)
.catch((err) => {
let parsedError = err;
parsedError.origin = "DATABASE_ERROR";
throw parsedError;
})
.then((response) => {
let newGuestID = response.id;
return COGNITO.post("user", "/user", newGuestID)
.then((res) => {
return res;
})
.catch((err) => {
let parsedError = err;
parsedError.origin = "COGNITO_ERROR";
throw parsedError;
});
})
.then((res) => {
//BOTH REQUESTS OCCURED WITH NO ERRORS
})
.catch((error) => {
if (error.origin === "COGNITO_ERROR") {
//IF DB CHANGES CONFIRMED BUT COGNITO FAILED, DELETE CREATED GUEST IN DB
return API.delete("guest", "/guest", guestInfo);
} else if (error.origin === "DATABASE_ERROR") {
//IF DB CHANGES FAILED THEN COGNITO HAS NOT RUN YET, SO DON'T NEED TO DELETE IN THIS CASE
}
});
};
But everything I've read says you should avoid nesting promises.
Alternatively, I could put API.post and COGNITO.post in separate functions with internal .then .catch statements, and then have those functions return a promise or throw an error with an added property to indicate origin. But I've seen people say that just hides the problem and makes code harder to follow.
The standard patter I see is that you have one catch, towards the end of a .then chain, that knows how to handle multiple kinds of errors. But if you don’t control the APIs you are working with, how can you confidently sort those errors? Is there something basic about the nature of errors in js that I'm missing?
Because you want to make the API calls in serial, this should be pretty easy to manage. All you need to do is do COGNITO.post in a .then after the first API call - no need to insert another .catch in between.
const signUpNewUser2 = (userInfo) => {
API.post("user", "/user", userInfo)
.then((response) => {
let newGuestID = response.id;
return COGNITO.post("user", "/user", newGuestID)
.then(handleBothSuccess)
.catch((err) => {
// COGNITO failed
return API.delete("guest", "/guest", guestInfo);
});
})
.then((res) => {
//BOTH REQUESTS OCCURED WITH NO ERRORS
})
.catch((error) => {
// Some error other than COGNITO failing occurred
});
};
There's nothing wrong with nesting Promises when the control flow you need to implement requires it - or with declaring the .then or .catch functions in separate standalone variables first, which avoids visual nesting.
Alternatively, consider async/await, which may be clearer to follow.
const signUpNewUser2 = async (userInfo) => {
let newGuestId;
try {
newGuestId = await API.post("user", "/user", userInfo);
} catch (e) {
// API failed, do something here if you want...
return;
}
let cognitoResponse;
try {
cognitoResponse = await COGNITO.post("user", "/user", newGuestID);
} catch (e) {
// COGNITO failed
// If deleting throws, the error will percolate to the caller
return API.delete("guest", "/guest", guestInfo);
}
//BOTH REQUESTS OCCURED WITH NO ERRORS
};
I want to display a custom message in Vue (with Django as the backend) when creating an account if the entered email already exists in the database.
Currently the server returns a 400 with the following response:
{"email":["There is another user with this email"]}
How can I access this error object to check if it contains an "email" key?
I've found this approach creating a wrapper: https://dev.to/sadarshannaiynar/capture-error-and-data-in-async-await-without-try-catch-1no2
but I feel like there must be a better/simpler way of handling this
Things I tried:
1) const { error, data } = await usersAPI.create(userData)
where "create" is:
create (data) {
return Vue.axios.post(url, data, config)
}
then console.error(error) (but it doesn't print anything)
2) Using try-catch(error) - prints nothing
3) Appending catch(e => console.error(e) to the await call - still nothing
Axios return an object error, you can get the content with error.message.
axios.post('/badurl')
.then(response => {
console.log(response)
})
.catch(error => {
console.log(error.message)
});
<script src="https://cdnjs.cloudflare.com/ajax/libs/axios/0.19.0/axios.min.js"></script>