I'm using the GraphQL Apollo Client. And I'm trying to retrieve the results from a query with errors.
GraphQL response
How do I retrieve the data, even when an error is returned in the same response?
This automatically catches the error. Instead, I want it to access the data.
this.client.query({
query: gql(query),
variables: variables
}).then(function ({ data }) {
return data;
}).catch(error => console.log(error));
Couldn't find anything in Apollo docs about this. Any ideas?
if you stumble here in future
In Apollo-client there are various error types as follow:
1. GraphQL Errors,
2. Server Errors,
3. Transaction Errors,
4. UI Errors,
5. Apollo Client Errors.
As # Alexander Schoonderwaldt you can learn about this errors and error policy here
You can handle/catch graphql errors using code below. If the values returns undefined, its no longer a graphql error. You need to investigate. You may have network error that returns Error CONNREFUSED or others.
.query({
query: myquery
})
.then(({ data }) => {
console.log(data.user);
return { loggedInUser: data };
})
.catch(errors => {
// Fail gracefully
console.log(errors.message); // log grapgql error
return { loggedInUser: {} };
});
Related
In the Apollographql documentation it states:
The onError link can retry a failed operation based on the type of GraphQL error that's returned. For example, when using token-based authentication, you might want to automatically handle re-authentication when the token expires.
This is followed up by their sample code:
onError(({ graphQLErrors, networkError, operation, forward }) => {
if (graphQLErrors) {
for (let err of graphQLErrors) {
switch (err.extensions.code) {
// Apollo Server sets code to UNAUTHENTICATED
// when an AuthenticationError is thrown in a resolver
case "UNAUTHENTICATED":
// Modify the operation context with a new token
const oldHeaders = operation.getContext().headers;
operation.setContext({
headers: {
...oldHeaders,
authorization: getNewToken(),
},
});
// Retry the request, returning the new observable
return forward(operation);
}
}
}
// To retry on network errors, we recommend the RetryLink
// instead of the onError link. This just logs the error.
if (networkError) {
console.log(`[Network error]: ${networkError}`);
}
});
My question is in regards to the getNewToken(), as no code was provided for this function, I want to know (assuming this is another request to the backend and I am not sure how it could not be), if you are able to and or supposed to use query/mutation in graphql or make the request through axios for example.
One problem, if it can/should be a graphql query or mutation, is to get the new token, the onError code is defined in the same file as the ApolloClient as ApolloClient needs access to onError, thus when trying to implement this as retrieving a new token through a graphql mutation I got the following error:
React Hook "useApolloClient" is called in function "refresh" that is
neither a React function component nor a custom React Hook function.
After trying to useQuery/useMutation hook and realizing I cannot outside of a react component and at the top level I found this post whose answers suggested you can use useApolloClient.mutate instead but I still ran into issues. My code was (and tried multiple iterations of this same code like useApolloClient() outside of the function and inside etc.):
const refresh = () => {
const client = useApolloClient();
const refreshFunc = () => {
client
.mutate({ mutation: GET_NEW_TOKEN })
.then((data) => {
console.log(data);
})
.catch((err) => {
console.log(err);
});
};
refreshFunc();
};
I could capitalize Refresh but this still would not work and would break the rules of hooks.
And to clarify all the above would do is I would replace the console.logs with setting session storage to the retrieved new token and then re trying the original request with onError.
Now in another post I found when looking into this, the users getNewToken request was a rest request using axios:
const getNewToken = async () => {
try {
const { data } = await axios.post(
"https://xxx/api/v2/refresh",
{ token: localStorage.getItem("refreshToken") }
);
localStorage.setItem("refreshToken", data.refresh_token);
return data.access_token;
} catch (error) {
console.log(error);
}
};
Now from my understanding, if I wanted to implement it this way I would have to change my backend to include express as I am only using apolloserver. Now I could definitely be wrong about that as my backend knowledge is quite limited and would love to be corrected their.
So my question is, what is the best way to do this, whether natively using graphql queries/mutations (if possible), doing it with axios, or maybe their is another best practice for this seemingly common task I am unaware of.
By simulating a Lie-Fi connection I noticed that I get an (net::ERR_QUIC_PROTOCOL_ERROR) error that didn't triggered the catch part of the fetch statement.
The reason that the fetch wasn't triggered is that the status code of the network request returned 200. Why is this Error OK (200)?.
But the main question is how can I detect and handle these errors?
This question gets more interesting in case the fetch is part of a fetch event handel of a serviceworker. For when the serviceworker tries to fetch something from the internet and if that isn't possible returns a result from the cache. These errors however will not be seen as an error and therefore the data from the cache will not be returned. As a user I then will not get the offline experience I would expect.
// please set Network Throttling to Lie-Fi (1kbit/s up&down)
const timeoutSimulation = () => {
return fetch('https://thispersondoesnotexist.com/image', {mode: 'no-cors'})
.then((response) => {
console.log(`would not expect to see this message, due to the net error`);
return response;
})
.catch((error) => {
console.log('expect this error to be shown', error);
})
}
I'm trying to use the Javascript fetch method, however, it does not seem to work asynchronously.
Here's my code:
fetch(`${global.URL}${url}`, requestConfig)
.then(res => res.json())
.then(res => {
console.log('response', res);
return res;
})
.catch(error => {
console.log('error: ', error)
})
I get the following error 70% of the time, then the other 30%, a valid response is received, when I save the file and it re-renders, it sometimes works.
error: SyntaxError: Unexpected token T in JSON at position 0
at parse (<anonymous>)
at tryCallOne (core.js:37)
at core.js:123
at JSTimers.js:277
at _callTimer (JSTimers.js:135)
at _callImmediatesPass (JSTimers.js:183)
at Object.callImmediates (JSTimers.js:446)
at MessageQueue.__callImmediates (MessageQueue.js:396)
at MessageQueue.js:144
at MessageQueue.__guard (MessageQueue.js:373)
I've tried calling it inside and async/await function but it does not help.
EDIT 1:
this is how I make my requests
const authenticityToken = global.TOKEN
const query = (url, config) => {
const requestConfig = {
credentials: 'same-origin',
...config,
headers: {
'X-Requested-With': 'XMLHttpRequest',
'Content-Type': 'application/json',
Accept: 'application/json',
Authorization: authenticityToken,
},
}
return fetch(`${global.URL}${url}`, requestConfig)
.then(res => res.json())
.then(res => {
console.log('response', res);
return res;
})
.catch(error => {
console.log('error: ', error)
})
// .then(handleResponseError)
}
export const get = (url, data) =>
query(data ? `${url}?${stringify(data)}` : url)
export function fetchUser() {
return (
get('/api/v3/me/')
)
}
Then I call the function inside my component like so:
const fetchUserAction = () => {
fetchUser()
.then((response) => {
if(response) setUser(response.data)
})
}
useEffect(() => {
fetchUserAction()
}, [])
This type of error usually happens when your server returns something which is not JSON. In my experience, 99% of the time the server is returning a generic error message. Often times servers will have a generic "catch all" error handler which returns something like:
There was an error processing your request.
In this case, if you tried to use JSON.parse (or res.json() in your case), you would get the error you are experiencing. To see this, paste this into your console:
JSON.parse("There was an error processing your request.")
//-> Uncaught SyntaxError: Unexpected token T in JSON at position 0
Solution 1: Usually the server will set a proper status code whenever there is an error. Check to make sure the response status is 200 before parsing:
fetch('...').then(res => {
if (res.status !== 200) {
throw new Error(`There was an error with status code ${res.status}`)
}
return res.json()
)
Solution 2: Update your server code to return an error message in JSON format. If you're using node and express, this would look something like this:
function errorHandler (err, req, res, next) {
if (res.headersSent) return next(err)
const message = 'There was an error processing your request.'
res.status(500)
if (req.accepts('json')) {
// The request contains the "Accept" header with the value "application/json"
res.send({ error: message });
return;
}
res.send(message);
}
Then, you would update your frontend code accordingly:
fetch('...')
.then(res => res.json())
.then(res => {
if (res.error) {
throw new Error(res.error)
}
return res
)
This kind of error Unexpected token T in JSON at position 0 always happens when the string you are trying to parse cannot be parsed as JSON. This specific error means that the string starts with the character 'T' and not with a '{' as strings that can be parsed to JSON should start. There's a very strict format that allows your string to become an object.
This is probably not the problem if you made sure on the backend that your code takes an object, stringifies it, and sends the text. If you know that on the backend the only thing that can be sent is a stringified object, there is probably nothing wrong there.
The second more plausible answer is that your request failed, I see you prepared a catch block in case the request returns an error, but there's a problem there. The request could have failed for several reasons, if you say it happens only some of the time it is probably CORS problems or a logical bug on the backend. In that case, you would like to see the response itself and not an already parsed response. What essentially happens is that when your request succeeds the body is successfully parsed to an object and everything works fine, but when the request fails, the response would be an exception that starts with a T, for example, a TimeoutException that when you try to parse it fails because it starts with a T and not as JSON. What you need to see is the response before it is parsed to JSON, and only if it is not an error, you should try to parse it.
The problem in your code is that the first thing you do is to try and parse it as JSON. I would suggest you comment out this line and simply print, either the successful request or the failed request as strings. I'm pretty sure you will find that in 70% of the time, you will see the JSON string that you expected and in the remaining 30, you will get an exception string (that might be even thrown automatically by your backend hosting service, like Timeout exceptions, they might not be treated as errors but as strings. This, unfortunately, happens a lot on the free plan of Firebase functions where the time a function is running is limited to a certain number of seconds, you should check it in the plans' description on their website) that starts with a T. This will most certainly help you find where the problem is by giving you more information.
On another note, I warmly recommend you to stop using then and catch and instead start using the far superior async/await syntax that helps you keep your code simple and organized. If it's compatible with all the engines you are targeting, read the Mozilla documentation about it, it's pretty straightforward: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Statements/async_function
Have a nice day and happy coding
I've got a handleSubmit function that fetches data from my backend as part of a larger component. I'd like to send the error information to my redux store and/or local component when the back-end fails, but am unable to do so.
The handleSubmit function looks like this (it's using React hooks, which are wired up correctly. Can post the full component if that is useful):
const handleSubmit = async (e, { dataSource, filter, filterTarget }) => {
e.preventDefault();
setIsLoading(true);
setErrorValue(null);
setError(false);
const token = localStorage.JWT_TOKEN;
const link = filterTarget === "Identifier" ? `http://localhost:8081/api/${dataSource}/${filter}`: `http://localhost:8081/api/${dataSource}?filter=${filter}&filterTarget=${filterTarget}`;
try {
let data = await axios.get(link, { headers: { authorization: token }});
props.setData(data);
setError(false);
setIsLoading(false);
} catch (err){
setErrorValue(err.message);
setError(true);
setIsLoading(false);
};
};
I'm intentionally making bad requests through the form, which will trigger an error in my backend. These are handled through my custom Express middleware function, which looks like this (I'll add more once I get this framework to work):
handleOtherError: (error, req, res, next) => { // Final custom error handler, with no conditions. No need to call next() here.
console.log("This error handler is firing!");
return res.status(500).json({
message: error.message,
type: "ServerError"
});
}
I know that this function is firing because the console.log statement is appearing on my server, and if I change the status code, so does the status code error on the front-end in my Google Chrome console.
In fact, if I go to the network tab, the correct error information appears for my request. Here's the video of me making a bad request:
However, when I try to access the err.message on my front-end, I'm not able to do so. The err.message in my try/catch handler for the handleSubmit function only ever gives me the Request failed with status code XXX
What am I doing wrong?
See https://github.com/axios/axios#handling-errors
You can access the response by using err.response.data.message, not err.message.
Found the answer posted elsewhere: https://github.com/axios/axios/issues/960
Apparently, to get the message, you have to use err.response.data.message
Simply using "err" will only give you a basic string respresentation of the error.
My app allows users to specify a server name which is where their installation of our api is. This is so the rest of the app can make calls to that endpoint.
However, I need to display an error message to if we get an error like ERR_NAME_NOT_RESOLVED and it seems that I'm not able to catch this as an error in javascript.
Is there any way around this?
What're you using to make the call to the endpoint? if you're using fetch you don't have an exception, the response is an object with a property error in true and the error messages.
response = {
error: true,
data: { message: 'ERR_NAME_NOT_RESOLVED', code: 404 }
}
If you're using axios, you have to make something like this:
axios.post('/formulas/create', {
name: "",
parts: ""
})
.then(response => {
console.log(response)
})
.catch(error => {
console.log(error)
});