Below code is making ajax request and getting data. As you can see then((response) => response.json()) is to convert response to json. But what if the http status is unauthorized or something else (400, 401 etc) and no json data is returned. Is there any better way to check status code before I convert the response to json?
I guess the catch is where I can do this, however, I want to keep catch for unhandled exceptions. Based on the status code I would like to show exact error user received(of course not for login component for security reasons). For example, Invalid Authorization, Invalid token.
My fetch code looks like this:
onLoginPressed() {
fetch('http://localhost:8080/api/user/login', {
method: 'POST',
headers: {
'Accept': 'application/json',
'Content-Type': 'application/json',
},
body: JSON.stringify({
username: this.state.username,
password: this.state.password
})
}).then((response) => response.json())
.then((responseJson) => {
if (responseJson.status === 500) {
Alert.alert(responseJson.message);
} else if(responseJson.profileCompleted == false){
this.props.navigation.navigate('SetupOne');
} else {
this.props.navigation.navigate('Home');
}
}).catch((error) => {
console.error(error);
});
}
If you look at the docs for fetch, you will see this:
The Promise returned from fetch() won’t reject on HTTP error status even if the response is an HTTP 404 or 500. Instead, it will resolve normally (with ok status set to false), and it will only reject on network failure or if anything prevented the request from completing.
And further down:
An accurate check for a successful fetch() would include checking that the promise resolved, then checking that the Response.ok property has a value of true.
So in other words, if this concerns you, you might want to check the status first, before doing your json body conversion. Something like the following:
const handleResponse = res => {
if(res.ok) {
return res.json()
}
throw new Error('Network response was not ok.')
}
fetch(someUrl, details)
.then(handleResponse)
.then(doYourWorkWithJSON)
.catch(err => console.error(err))
Related
I was writing a little wrapper for fetch method in JavaScript (I am very aware of libs like Axios that can do the same thing). I got the idea from a blog post
My code looks like this
async function apiCall(
endpoint,
{ data, headers: customHeaders, ...customConfig } = {}
) {
console.log("endpoint", endpoint);
const config = {
method: data ? "POST" : "GET",
body: data ? JSON.stringify(data) : undefined,
headers: {
"content-type": data ? "application/json" : undefined,
...customHeaders
},
...customConfig
};
return window.fetch(endpoint, config).then(async (response) => {
if (response.ok) {
return response.json();
} else {
// By default, window.fetch will only reject a promise if the actual request itself failed (network error), not if it returned a "Client error response".
const error = await response
.json()
.catch(() => new Error("invalid json"));
return Promise.reject(error);
}
});
}
export function requestMovies(query) {
const endpoint = `${apiULR}?apikey=${API_KEY}&s=${encodeURIComponent(query)}`;
return apiCall(endpoint);
}
However, I encountered TypeError Failed to fetch which I believed is caused by CORS.
If I take out config from window.fetch as in
async function apiCall(
endpoint,
{ data, headers: customHeaders, ...customConfig } = {}
) {
return window.fetch(endpoint).then(async (response) => {
if (response.ok) {
return response.json();
} else {
// By default, window.fetch will only reject a promise if the actual request itself failed (network error), not if it returned a "Client error response".
const error = await response
.json()
.catch(() => new Error("invalid json"));
return Promise.reject(error);
}
});
}
The problem would be gone. Not sure which part exactly triggered this CORS problem...
Here is a live demo: https://codesandbox.io/s/charming-saha-4c2bh?file=/src/index.js
follow the data not given path:
the ternary goes into the false case
headers gets an entry content-type: undefined
the request gets this header added
request is rejected by api because it contains a content-type header (probably with the string 'undefined' in it)
Solution: Dont use a ternary here, and replace it with an if, to get rid of the undefined entry.
Also: read up on differences between null, undefined values and "has own property" in javascript objects
I have the following React Native client code:
confirmMatchRecord(userId, matchedUserData['userId'], matchRecordData['matchRecord']['matchWinner'], matchRecordData['matchType'], currentUserRating, matchedUserRating, matchData['_id'], matchRecordData['matchRecord']['_id'], airbnbRatingValue, true, new Date())
.then((results) => {
// Do stuff
})
.catch((error) => {
Alert.alert('Error', 'There was an issue with confirming the record. Please check your connection and/or try again later.');
});
And the following code in my confirmMatchRecord function:
export async function confirmMatchRecord(userId, matchedUserId, matchWinner, matchType, currentUserRating, matchedUserRating, matchId, matchRecordId, matchRating, matchConfirmed, timestamp) {
console.log('Attempting to record match');
info = { userId, matchedUserId, matchWinner, matchType, currentUserRating, matchedUserRating, matchId, matchRecordId, matchRating, matchConfirmed, timestamp }
const firebaseIdToken = await AsyncStorage.getItem('#firebaseIdToken')
const requestOptions = {
method: 'POST',
headers: { 'Content-Type': 'application/json', 'Authorization': 'Bearer ' + firebaseIdToken },
body: JSON.stringify(info)
};
const response = await fetch(ngrokOrLocalhost + '/confirmmatchrecord', requestOptions)
if (response['Status']==='Failure') {
// throw new Error(`HTTP error! status: ${response.status}`);
throw new Error(400);
} else if (response['Status']==='Success') {
const data = await response.json()
return data
}
}
Server code:
router.post('/confirmmatchrecord', async (req, res) => {
// Do a lot of stuff
if (response==='Success') {
return res.status(200).json({'Status': 'Success'})
} else {
return res.status(400).json({'Status': 'Failure'})
console.log('Match record was not confirmed successfully');
}
When response['Status']==='Failure (sent by server) it throws an error 400 as you can see, I was hoping to trigger the .catch in the client code then. But that does not happen, because the client code continues to run on the .then part.
How should I do this instead? Not sure if using .catch here is even correct or if I should do this type of work another way.
You seem to be aware of the bit of a footgun in the fetch API (I write about it here) where fetch only rejects its promise on network errors, not HTTP errors, but your check is incorrect in a couple of ways:
It's status, not Status (capitalization matters), and
It's the HTTP code (400 for instance), not a string
The Response object provides a convenient ok flag that's true for any successful response and false otherwise, so:
const response = await fetch(ngrokOrLocalhost + '/confirmmatchrecord', requestOptions)
if (!response.ok) {
throw new Error(`HTTP error! status: ${response.status}`); // This will be "HTTP error! status: 400" if the HTTP error code is 400
}
const data = await response.json();
return data;
In a comment you've said:
My response['Status'] was checking for a custom server message I had sent (res.status(400).json({'Status': 'Failure'}), I updated the post with it. Not sure why it didn't catch that
Ah! Okay. The reason it didn't catch it is that you're looking for it on the Response object, but your JSON is in the response body.
I suspect you don't want to use your own Status anymore since you know about response.ok and response.status now, but if you ever do want to include your own information in an error response as JSON, you can do that. You'd do it like this:
const response = await fetch(ngrokOrLocalhost + '/confirmmatchrecord', requestOptions)
const data = await response.json(); // Expects JSON in *both* the success response and the error response
if (data.Status === "Failure") {
throw new Error(`HTTP error! status: ${response.status}`); // This will be "HTTP error! status: 400" if the HTTP error code is 400
}
return data;
But I'd stick with just the built-in ok and status for pure success/failure information. This could be handy if you wanted to provide more details of the failure, though.
I need to catch error 401 Code of response so that I can retry after getting a new token from token endpoint. I am using fetch method get data from API.
const request: Request = new Request(url.toString(), {
headers: this.defaultRequestHeaders,
method: "get",
mode: "cors"
});
const headers: Headers = new Headers({
"Accept": "application/json",
"Content-Type": "application/json"
});
fetch(request)
.then(function(response)
{
///Logic code
})
.catch(function(error)
{
///if status code 401. Need help here
});
You can check the status and if it's not 200 (ok) throw an error
fetch("some-url")
.then(function(response)
{
if(response.status!==200)
{
throw new Error(response.status)
}
})
.catch(function(error)
{
///if status code 401...
});
Because 401 is actually a valid response to a request to a server, it will execute your valid response regardless. Only if security issues occur, or if the server is unresponsive or simply not available will the catch clause be used. Just think of it like trying to talk to somebody. Even if they say "I am currently not available" or "I don't have that information", your conversation was still successful. Only if a security guy comes in between you and stops you from talking to the recipient, or if the recipient is dead, will there be an actual failure in conversation and will you need to respond to that using a catch.
Just separate out your error handling code so you can handle it in instances that the request was successful, but does not have the desired outcome, as well as when an actual error is being thrown:
function catchError( error ){
console.log( error );
}
request.then(response => {
if( !response.ok ){
catchError( response );
} else {
... Act on a successful response here ...
}
}).catch( catchError );
I am using the response.ok suggested by #Noface in the comments, as it makes sense, but you could check for only the response.status === 401 if you want to.
You can try this
fetch(request)
.then(function(response) {
if (response.status === 401) {
// do what you need to do here
}
})
.catch(function(error) {
console.log('DO WHAT YOU WANT')
});
You can check the status of the response in then:
fetch(request)
.then(function(response) {
if (response.status === 401) {
// do what you need to do here
}
})
.catch(function(error) {});
fetch(url,{
method: 'GET',
headers,
body: JSON.stringify(aData)
}).then(response => {
if(response.ok){
return response.json();
}
return Promise.reject(response);
}).catch(e => {
if(e.status === 401){
// here you are able to do what you need
// refresh token ..., logout the user ...
console.log(e);
}
return Promise.reject(e.json());
});
(function () {
var originalFetch = fetch;
fetch = function() {
return originalFetch.apply(this, arguments).then(function(data) {
someFunctionToDoSomething();
return data;
});
};})();
source
Can one use the Fetch API as a Request Interceptor?
When you want to...
catch (error) {
console.dir(error) // error.response contains your response
}
I got this kind of response from fetch when I inspect it in the console, as shown in the picture. However, when i check from the devtool Network, it shows the correct error response. Any idea on how to deal with this?
export function loginRequest(data){
return (dispatch, getState) => {
let tmp = Object.assign({},data)
var request = new Request('https://aaa.com/json', {
method: 'POST',
mode: 'cors',
headers: new Headers({
'Content-Type': 'text/plain'
})
});
fetch(request).then((res)=>{
alert(JSON.stringify(res))
dispatch({
type: types.LOGIN,
data: res
})
}).catch(err =>{
alert(JSON.stringify(err))
alert(err.errMsg)
dispatch({
type: types.LOGIN,
data: data
})
console.log(JSON.stringify(err))
})
}
}
You need to parse the response to JSON to see the response that matches what you see in the network tab.
fetch('//offline-news-api.herokuapp.com/stories')
// First you can deal with the response, checking status code, headers, etc.
.then(function(response) {
if (response.status >= 400) {
throw new Error("Bad response from server");
}
// This is the line you are missing
return response.json();
})
// Now you will see the proper JSON response that should match
// what you see in the network tab.
.then(function(stories) {
console.log(stories);
});
When using fetch, you can see the raw response, which is what your screenshot seems to be, and then you can parse it to JSON to see the body of the response as you are expecting. This allows more fine-tuned control of error handling, responses, etc.
You can also use axios where you don't have to parse your response and don't have to manually reject if there's 400 errors, it will go to catch instead.
I am sending a status code 422 from my backend code with response body which contains the description of the error. I am using axios post as below to post a request:
post: function(url, reqBody) {
const request = axios({
baseURL: config.apiUrl,
url: url,
headers: {
'Content-Type': 'application/json',
'Authorization': sessionStorage.getItem('token')
},
method: 'POST',
data: reqBody,
responseType: 'json'
});
return request
.then((res) => {
return res;
})
.catch((error) => {
console.log(error);
return error;
})
}
The problem is when backend is returning error code 422, the error object I am catching has no information about response body. Is there any way I can retrieve the error text?
I had this same issue and the answer (as per Axios >= 0.13) is to specifically check error.response.data:
axios({
...
}).then((response) => {
....
}).catch((error) => {
if( error.response ){
console.log(error.response.data); // => the response payload
}
});
See here for more details.
The "body" of an AXIOS error response depends from the type of response the request had.
If you would like full details about this issue you can see this blogpost: How to catch the body of an error in AXIOS.
In summary AXIOS will return 3 different body depending from the error:
Wrong request, we have actually done something wrong in our request (missing argument, bad format), that is has not actually been sent. When this happen, we can access the information using error.message.
axios.get('wrongSetup')
.then((response) => {})
.catch((error) => {
console.log(error.message);
})
Bad Network request: This happen when the server we are trying to reach does not respond at all. This can either be due to the server being down, or the URL being wrong.
In this case, we can access the information of the request using error.request.
axios.get('network error')
.then((response) => {})
.catch((error) => {
console.log(error.request );
});
Error status: This is the most common of the request. This can happen with any request that returns with a status that is different than 200. It can be unauthorised, not found, internal error and more. When this error happen, we are able to grasp the information of the request by accessing the parameter specified in the snippets below. For the data (as asked above) we need to access the error.response.data.
axios.get('errorStatus')
.then((response) => {})
.catch((error) => {
console.log(error.response.data);
console.log(error.response.status);
console.log(error.response.headers);
})
For those using await/async and Typescript
try {
const response = await axios.post(url, body)
} catch (error) {
console.log(error.response.data);
}
For react native it just worked for me
api.METHOD('endPonit', body)
.then(response => {
//...
})
.catch (error => {
const errorMessage = JSON.parse(error.request.response)
console.log(errorMessage.message)
})
We can check error.response.data as #JoeTidee said. But in cases response payload is blob type? You can get error response body with the below code.
axios({
...
}).then((response) => {
....
}).catch(async (error) => {
const response = error.response
if(typeof response.data.text === function){
console.log(await response.data.text()); // => the response payload
} else {
console.log(response.data)
}
});
I am returning a string from backend but expecting a json as response type. So I need to return an object instead of string for axios to process it properly.
In my case I wanted to retrieve a response 404 error message (body).
I got body with error.response.data but I couldn't display it because the type was ArrayBuffer.
Solution:
axios.get(url, { responseType: 'arraybuffer' }).then(
response => {...},
error => {
const decoder = new TextDecoder()
console.log(decoder.decode(error.response.data))
}
)
Related posts:
Converting between strings and ArrayBuffers