Handling status 500 with try and catch - javascript

I can't tell what is the optimal way to handle the status 500 sent by my API. Could you please help me?
In the case below, when my API responds with a status 500, I wish to set an error message in my redux store. When my API sent a successful status code, I wish to "Fetch the user decks" in my store.
My first attempt felt logical and dry to me, but it did not work:
const createDeck = async () => {
try {
const request = await fetch(`${back}/deck/`, options)
const response = await request.json()
store.dispatch({ type: FETCH_USER_DECKS })
} catch (error) {
store.dispatch({ type: SET_ERROR, message: error })
}
}
When the API send a 500 status, no exception seems to be thrown and the code in my catch block is ignored.
My second and third attempt worked as I excepted them too, but they feel convoluted and weird:
2nd attempt:
const createDeck = async () => {
try {
const request = await fetch(`${back}/deck/`, options)
const response = await request.json()
if (request.status === 201 || request.status === 200) {
store.dispatch({ type: FETCH_USER_DECKS })
} else {
store.dispatch({ type: SET_ERROR, message: error })
}
} catch (error) {
console.log('error')
}
}
This works, but it ignores completely the catch block, making me wonder: what's even the point of try...catch then?
Third attempt:
const createDeck = async () => {
try {
const request = await fetch(`${back}/deck/`, options)
const response = await request.json()
if (request.status === 201 || request.status === 200) {
store.dispatch({ type: FETCH_USER_DECKS })
} else {
throw response
}
} catch (error) {
store.dispatch({ type: SET_ERROR, message: error })
}
}
This third attempt make use of catch, but it feels weird to manually throw an exception, to be immediately caught by catch.
Am I missing something here?

Isbn has properly answered this in the comments. I'm putting it here again for visibility:
"Since you're using fetch() it would probably make sense to adopt its implementation model and to not consider a 500 response as an exception (attempt 2). But I wouldn't see anything wrong with your third attempt either (axios, for example, would raise an error in that case). Note that fetch() response exposes a ok property to check whether the request succeeded or not"

Related

Is it necessary to check if axios response status is 200 or not?

Is it necessary to check if axios.get() response status is 200 ?
useEffect(() => {
const fetch = async () => {
try {
const response = await axios.get('/api');
if (response.status === 200) {
setState(response.data);
}
} catch (error) {
console.error(error);
}
};
fetch();
}, []);
or I can do this
useEffect(() => {
const fetch = async () => {
try {
const { data } = await axios.get('/api');
setState(data);
} catch (error) {
console.error(error);
}
};
fetch();
}, []);
if it is, what is best practice?
Normally you don't need to check. But technically, it totally depends on how your team interpret HTTP protocol.
Even though it’s discouraged, I’ve seen teams that totally disregard the standard semantics of HTTP status code, blindly set status code to 200 in almost all cases, then encode the success/failure state within data payload, e.g. data.success == true or false. In that case checking status code means nothing.
Axios is just a library that facilitates message exchanging over HTTP. The real working horse is HTTP protocol. You can even customize axios to determine what case is deemed "error", like shown in this question. Better consult your backend colleagues to understand their server response convention and reach consensus within your team.

How to get data from node.js when using a post request in react

I am creating a mern authentication project and am stuck on a problem. When the information is sent in my register page an add user function is called on the front end
async function addUser(user) {
const config = {
headers: {
"Content-Type": "application/json",
},
};
try {
await axios.post("/users/register", user, config);
} catch (err) {}
}
Which calls this function in my back end
exports.addUser = async (req, res, next) => {
try {
const { name, email, password } = req.body;
let errors = [];
// Check required fields
if (!name || !email || !password) {
errors.push("All Fields Need To Be Filled");
}
//Check Password Length
if (password.length < 6) {
errors.push("Password Needs To Be At Least 6 Characters Long");
}
if (errors.length > 0) {
return res.status(201).json({
success: false,
errors,
});
} else {
return res.status(201).json({
success: true,
data: req.body,
});
}
} catch (error) {
return res.status(500).json({
success: false,
error,
});
}
};
And my route
router.route("/register").post(addUser)
My question is how to get the json from the node.js function in react.js. For example if there is an error how do I get this
return res.status(201).json({
success: false,
errors,
in my front end. Since this is not a get request I can't access the errors with
const res = await axios.get("/");
If anybody knows what to do it would mean a lot if you helped
First off, I would recommend changing the code as a response of 201 is considered a successful "Created" response (mozilla docs).
Then, if you are using axios, you need to create an interceptor in your frontend side that will handle the response errors. Check the documentation here.
Basically you can leave the response part empty, and then throw an exception in the error function so you can handle it from the place you are making the call.
axios.interceptors.response.use(
(response) => response,
(error) => {
// you can handle the error here or throw an error
throw error;
}
})
I recommend that you leave the interceptor and the base axios call in a separate file so it's easier for you to handle the calls and the interceptor.

Update state inside catch in javascript

I'm currently working on a script that sends requests to a 3rd party website, but after sometime the cookies are invalid & it throws a 403
So I've got my script to send the request, if it throws the 403 then we send VALID cookies and then RESEND the request.
However it appears that the state.device_info remains undefined after updating it inside the catch function?
(async () => {
let state = {
'access_denied': false,
'cookies': {}
};
try {
await getCookies(state);
state.device_info = await getInfo(state).catch(async (error) => {
let localError = handleError(error, state);
if(localError === 'access_denied') {
state.access_denied = true;
//now lets unlock the request & send it again!
let post = await sendValidCookies(state);
if(post.data.success === true) {
//update state.device_info with WORKING request!
state.device_info = await getInfo(state).catch(async (error) => {
console.log('Damn we got another error!');
console.log(error);
})
if(state.device_info.status === 200) {
console.log('We got our info info using the unlocked request!');
state.access_denied = false;
}
}
}
});
if(state.device_info === undefined || state.access_denied === true) {
console.log('We have an undefined value!');
console.log(state.device_info); //undefined
console.log(state.access_denied); //false
return false;
}
} catch(error) {
console.log('major error!');
}
})();
Instead of defining it inside catch, just return the value. This returned value will be assigned to state.device_info
state.device_info = await getInfo(state).catch(async (error) => {
let localError = handleError(error, state);
if(localError === 'access_denied') {
state.access_denied = true;
//now lets unlock the request & send it again!
let post = await sendValidCookies(state);
if(post.data.success === true) {
//Get result from WORKING request
const result = await getInfo(state).catch(async (error) => {
console.log('Damn we got another error!');
console.log(error);
});
if(state.device_info.status === 200) {
console.log('We got our info info using the unlocked request!');
state.access_denied = false;
}
return result;
}
}
});
Since you're using the await keyword in front of your method call getInfo(state) and you're calling .catch(...) right after, I assume getInfo(state) returns a Promise.
There are two ways you can handle Promise in JavaScript:
By explicitly call .then(result => {...}) while catching errors with .catch(error => {...}).
By putting the await keyword in front of your method call. By using this syntax, and if you want to catch errors (which you should do), you need to use the try { } catch (error) { } syntax.
I've never mix those two methods like you did and I think this might be the origin of the issue you're experiencing.
For your case, it should look like this:
try {
state.device_info = await getInfo(state);
} catch (error) {
// Your logic here...
}

Can I throw error in axios post based on response status

Is it possible to throw an error on purpose inside the .then() block in axios? For instance, if the api responds with 204 status code, could I throw an error and run the catch block?
For example:
axios.post('link-to-my-post-service', {
json-input
}).then(response => {
if (response.status === 200) {
//proceed...
}
else {
// throw error and go to catch block
}
}).catch(error => {
//run this code always when status!==200
});
EDIT
I tried this, but it didn't work:
var instance = axios.create({
validateStatus: function (status)
{
return status == 200;
}
});
axios.post('link-to-my-post-service', {input: myInput}, instance)
.then(response => {
dispatch({
type: "FETCH_SUCCESS",
payload: response.data
});
}).catch(error => {
dispatch({
type: "FETCH_FAILED",
payload: error
});
});
When I get a status code 204, still the executed block is then() block instead of the catch block.
EDIT 2
The correct answer using Ilario's suggestion is this:
var instance = axios.create({
validateStatus: function (status)
{
return status == 200;
}
});
instance.post('link-to-my-post-service', {input: myInput})
.then(response => {
dispatch({
type: "FETCH_SUCCESS",
payload: response.data
});
}).catch(error => {
dispatch({
type: "FETCH_FAILED",
payload: error
});
});
Now when the status code is not equal to 200 the catch block code is executed.
If you give a look at the GitHub Project Page you will notice following option description.
/* `validateStatus` defines whether to resolve or reject the promise for a given
* HTTP response status code. If `validateStatus` returns `true` (or is set to `null`
* or `undefined`), the promise will be resolved; otherwise, the promise will be
*/ rejected.
validateStatus: function (status) {
return status >= 200 && status < 300; // default
},
So you could create an Instance with your own configuration.
var instance = axios.create({
validateStatus: function (status) {
return status == 200;
},
});
You could also set defaults. These will be applied to every request.
axios.defaults.validateStatus = () => {
return status == 200;
};
UPDATE 1
To set the config only on a specific operation you could replace "config" with your desired values or methods.
axios.post(url[, data[, config]])
UPDATE 2
I tried this, but it didn't work.
You cannot pass the instance to axios.post(). You must call post on the new instance.
var instance = axios.create({
validateStatus: function (status) {
return status == 200;
}
});
instance.post('url', data, config);
Thank you very much for your suggestions. The answer was simpler than I expected.
I didn't want to set any default options to change the behavior of axios, so I just tried something like the code below, and it worked. Every time the code throw new Error("Error"); is executed, the catch block code is executed after that.
axios.post('link-to-my-post-service', {
json-input
}).then(response => {
if (response.status === 200) {
//proceed...
}
else {
// throw error and go to catch block
throw new Error("Error");
}
}).catch(error => {
//when throw "Error" is executed it runs the catch block code
console.log(error)
});

Reading the body returned for fetch() response which contains an HTTP error code [duplicate]

I have an HTTP API that returns JSON data both on success and on failure.
An example failure would look like this:
~ ◆ http get http://localhost:5000/api/isbn/2266202022
HTTP/1.1 400 BAD REQUEST
Content-Length: 171
Content-Type: application/json
Server: TornadoServer/4.0
{
"message": "There was an issue with at least some of the supplied values.",
"payload": {
"isbn": "Could not find match for ISBN."
},
"type": "validation"
}
What I want to achieve in my JavaScript code is something like this:
fetch(url)
.then((resp) => {
if (resp.status >= 200 && resp.status < 300) {
return resp.json();
} else {
// This does not work, since the Promise returned by `json()` is never fulfilled
return Promise.reject(resp.json());
}
})
.catch((error) => {
// Do something with the error object
}
// This does not work, since the Promise returned by `json()` is never fulfilled
return Promise.reject(resp.json());
Well, the resp.json promise will be fulfilled, only Promise.reject doesn't wait for it and immediately rejects with a promise.
I'll assume that you rather want to do the following:
fetch(url).then((resp) => {
let json = resp.json(); // there's always a body
if (resp.status >= 200 && resp.status < 300) {
return json;
} else {
return json.then(Promise.reject.bind(Promise));
}
})
(or, written explicitly)
return json.then(err => {throw err;});
Here's a somewhat cleaner approach that relies on response.ok and makes use of the underlying JSON data instead of the Promise returned by .json().
function myFetchWrapper(url) {
return fetch(url).then(response => {
return response.json().then(json => {
return response.ok ? json : Promise.reject(json);
});
});
}
// This should trigger the .then() with the JSON response,
// since the response is an HTTP 200.
myFetchWrapper('http://api.openweathermap.org/data/2.5/weather?q=Brooklyn,NY').then(console.log.bind(console));
// This should trigger the .catch() with the JSON response,
// since the response is an HTTP 400.
myFetchWrapper('https://content.googleapis.com/youtube/v3/search').catch(console.warn.bind(console));
The solution above from Jeff Posnick is my favourite way of doing it, but the nesting is pretty ugly.
With the newer async/await syntax we can do it in a more synchronous looking way, without the ugly nesting that can quickly become confusing.
async function myFetchWrapper(url) {
const response = await fetch(url);
const json = await response.json();
return response.ok ? json : Promise.reject(json);
}
This works because, an async function always returns a promise and once we have the JSON we can then decide how to return it based on the response status (using response.ok).
You would error handle the same way as you would in Jeff's answer, however you could also use try/catch, an error handling higher order function, or with some modification to prevent the promise rejecting you can use my favourite technique that ensures error handling is enforced as part of the developer experience.
const url = 'http://api.openweathermap.org/data/2.5/weather?q=Brooklyn,NY'
// Example with Promises
myFetchWrapper(url)
.then((res) => ...)
.catch((err) => ...);
// Example with try/catch (presuming wrapped in an async function)
try {
const data = await myFetchWrapper(url);
...
} catch (err) {
throw new Error(err.message);
}
Also worth reading MDN - Checking that the fetch was successful for why we have to do this, essentially a fetch request only rejects with network errors, getting a 404 is not a network error.
I found my solution at MDN:
function fetchAndDecode(url) {
return fetch(url).then(response => {
if(!response.ok) {
throw new Error(`HTTP error! status: ${response.status}`);
} else {
return response.blob();
}
})
}
let coffee = fetchAndDecode('coffee.jpg');
let tea = fetchAndDecode('tea.jpg');
Promise.any([coffee, tea]).then(value => {
let objectURL = URL.createObjectURL(value);
let image = document.createElement('img');
image.src = objectURL;
document.body.appendChild(image);
})
.catch(e => {
console.log(e.message);
});
Maybe this option can be valid
new Promise((resolve, reject) => {
fetch(url)
.then(async (response) => {
const data = await response.json();
return { statusCode: response.status, body: data };
})
.then((response) => {
if (response.statusCode >= 200 && response.statusCode < 300) {
resolve(response.body);
} else {
reject(response.body);
}
})
});

Categories