Mjuice Mjuice - 1 month ago 13
HTTP Question

The success block of my promise always executes even when I return a 404

When a user logs in with incorrect email and password, the success block of my client-side promise still executes, even though the server returned a 400.

I'm using Redux with React so I'm calling an action creator which calls an HTTP request using axios. I need help understanding why I'm not handling errors correctly, because the rest of my app's auth functions like signup, logout, etc all behave the same way even though I'm returning 400 statuses from the server.

Here is where I call login from my component, the success block always executes:

handleFormSubmit({
email, password
}) {
this.props.loginUser({
email, password
}).then(() => {
toastr.success("You are logged in!");
}).catch(() => {
toastr.warning("Could not log in");
})
}


Here is the action creator "loginUser", the success block for this function does NOT run when I return a 400 from the server:

export function loginUser({ email, password }) {

return function(dispatch) {

return axios.post(`/users/login`, {
email, password
})
.then(response => {

dispatch({
type: AUTH_USER
});


localStorage.setItem('token', response.headers['x-auth']);
browserHistory.push('/feature');
})
.catch(() => {
dispatch(authError('Incorrect email or password'));
});
}
}


Here is the route '/users/login' Please note that the 400 status does in fact return:

app.post('/users/login', (req, res) => {
var body = _.pick(req.body, ['email', 'password']);

User.findByCredentials(body.email, body.password).then(user => {
return user.generateAuthToken().then(token => {
res.header('x-auth', token).send(user);
});
}).catch(e => {
res.status(400).send();
});
});

Answer

You issue is that you're misunderstanding what a catch clause is in promises.

The way you can think if it is just a then with a rejection handler:

function(_, reject) {
  reject(Error('Some error'))
}

Meaning it only handles the error from the previous promise, and you can continue chaining stuff after it no matter what happened.

Example:

new Promise((resolve, reject) => {
  setTimeout(() => reject(Error('After 1 sec')), 1000)
})
.catch((err) => {
  console.log(`catch: ${err}`);
  return 5;
})
.then((five) => {
 // this chains because the error was handled before in the chain
 console.log(`catch.then: ${five}`); // 5
})
.catch(() => {
  console.log('No error happened between this error handler and previous so this is not logged');
});

To make an error propagate from the current catch to the next error handler, you can return a rejected Promise (or re-throw the error) to make the chain skip all the success handlers until the next fail (or catch) handler.

new Promise((resolve, reject) => {
  setTimeout(() => reject(Error('After 1 sec')), 1000)
})
.catch((err) => {
  // return a reject promise to propagate to the next error handler
  return Promise.reject(err);
  // can also `throw err;`
})
.then((nothing) => {
 // this doesn't happen now
 console.log(nothing);
})
.catch(console.error); // this logs the error

Comments