Handling server errors with .catch

Skylar S
SkyTech
Published in
4 min readJan 8, 2019

A few days ago, I built out the front end of a task management application. Everything was going smoothly, until I realized my front end had no ability to handle errors gracefully. For example:

  • What if our user input was invalid?
  • What if my server encountered an error?
  • What if my front end attempted to access a resource that didn’t exist?

In these instances, I’d expect a response from the server, with some error messages. I’d want to break out of our normal execution path, so that app doesn’t try to process this erroneous data as if it were the JSON data it was expecting. I’d also want to log this error data, along with the HTTP response codes, if they weren’t included.

So I did a little research. Here’s what you need to know:

Promises have a .catch method

It takes a callback, and it works like this: You stick “.catch(callback)” onto the end of a promise. If the promise succeeds, the ‘then’ callback will be called; otherwise, the ‘catch’ callback will be called.

Fetch(‘notawebsite.com/api’).catch(error => console.log(’sorry, couldn’t connect’)
// "Sorry, couldn't connect

You can throw an error inside a ‘then’ statement, and handle it inside the catch

For example, a simple way of handling http errors is:

Fetch(‘website.com/api').then((resp) =>{
if(!resp.ok){throw Error(resp.statusText)}
return resp.json()}).then((json)=> doSomething(json)).catch(console.log)

In the above example, if the server responds with a status code 300 or over, doSomething will never be called; instead, the catch callback will be called, and the response’s statusText text will be logged.

You can also throw the response object itself, like so:

Fetch(‘website.com/api).then((resp) =>{
if(!resp.ok){throw resp}
return resp.json()
}).then(json=> doSomething(json)).catch(resp=>console.log(resp.status, resp.statusText)

Here, because we have the entire response object instead of just an error message, we are able log both the status and the statusText.

Or, you can use Promise.reject

Fetch(‘website.com/api).then((resp) =>{
if(!resp.ok){return Promise.reject(resp)}
return resp.json()
}).then(json=> doSomething(json)). catch(resp=>console.log(resp.status, resp.statusText))

Note the use of a return statement here. Errors break out of the function automatically, but rejecting the promise does not.

Don’t forget that order matters

Once you handle the error with .catch, the promise chain will continue executing. So if you call .catch early, it will allow anything below it to execute, even if there was an error above it.

// response is not okFetch(‘website.com/api).then((resp) =>{
if(!resp.ok){return Promise.reject(resp)}
return resp.json()
})
.then(json=> doSomething(json)) // WON'T RUN
.catch(resp=>console.log(resp.status, resp.statusText))
/ VS Fetch(‘website.com/api).then((resp) =>{
if(!resp.ok){return Promise.reject(resp)}
return resp.json()
})
.catch( (resp)=>console.log(resp.status, resp.statusText))
.then((json)=> doSomething(json)) // WILL RUN

Nested ‘then’ statements allow you to retain scope

Say we are given an error in the JSON-API format. (See https://jsonapi.org/examples/#error-objects for more details.)

We could handle this error like so:

Fetch(‘website.com/api’).then((resp)=>resp.json()).then((resp) => {  if (json.errors){showErrors(json.errors)}  else{doSomething(json)}
})

In the code above, we are first checking if there are error messages, and if there are error messages, we pass them to another function to display the messages. But what if resp.errors doesn’t always include an error code (which we’ve decided to show the user)? Don’t fear:

Fetch(‘website.com/api’).then(function (resp) {  return resp.json().then((json) => {    if(!resp.ok){
showErrors(resp.status,json.errors)}
else{doStuff(json)
}
})
})

Here, we call resp.json inside the original ‘then’ callback, with another callback chained on. Because we are still inside of the first ‘then’ callback, we still have access to the response object, along with access to our JSON data.

Handling Ruby on Rails Exceptions and Validation Errors

When Rails encounters certain kinds of errors, including routing and active record errors, it’ll often return JSON that looks like this:

{
status: 404,
error: "Not Found",
exception: "#<ActiveRecord::RecordNotFound: Couldn't find User with 'id'=7>",
traces: {…}
}

We have a really helpful message here about what went wrong! It even includes HTTP code and status text. Let’s use what we’ve learned to check if the response is okay, and also log this JSON:

Fetch(‘website.com/api’).then(function (resp) {  return resp.json().then((json) => {     if(!resp.ok){throw json}})
.then((json)=> doSomething)// this json contains the normal response
.catch((json)=>console.log(json))//this json contains an error object

What about validation errors? This depends on what’s in the controller. One approach might be:

def create
@list=List.create(list_params)
if @list.valid?
render json: @list, status: :ok
else
render json: {errors: @list.errors}, status: 422
end

This isn’t JSON-API format, but it’s good enough for small projects. In the case of a http response with a status code over 300, let’s have our app log the whole JSON response object (which could contain either validation errors or an exception message), and if our response has the errors key, assume those are validation errors and show them to the user.

Fetch(‘website.com/api’).then(function (resp) {return resp.json().then((json) => {if(!resp.ok){throw json}})
.then((json)=> doSomething)// this json contains the normal response
.catch((json)=>{
console.log(json)
if(json.errors){showErrorsOnPage(json.errors)}
}

--

--