How many times have you been using a web app (or any other computer program for that matter) when things just…stopped working? It is very unlikely that we will ever be able to stop errors from occurring in our apps. What we can do, though, is try and set things up so that errors are reported clearly and do not simply cause our app to crash. For this purpose, we need error handling.
Error handling
In everything we code, we should do our best to make our code as robust and reliable as possible. Basic JavaScript error handling is often achieved with try...catch...finally
blocks, and although this can be good enough in some cases, there are many strategies for code reliability.
For example, if you have a function that requires three arguments, will you use a try/catch block to try and manipulate them? Or would it be better to test for the arguments before doing anything and sending back a meaningful error message? Or should you use default values? There is no single "right answer" to these questions, but what would be wrong is to ignore the problem completely.
Let's go back over the code from the previous chapter and see how we can make it more robust.
Practice!
Head to CodePen Exercise P3CH4a and follow the instructions below.
Let's go down through our code and see where improvements can be made without sacrificing too much performance.
The first thing I notice is our makeRequest()
function. We allow for three arguments (which we need to do), but we will only be passing the third data
argument if we want to make a POST request. In fact, the data
argument is mandatory for a POST request.
Now, we could easily make a check in the first line of our function which would return 0 if someone tried to make a POST request without a data
object. However, this would wreck our entire async
function. Perhaps we should find a better solution.
Perhaps we should place the check inside our Promise and reject it with a custom error object if the check fails.
Just inside the beginning of our Promise, create a check where, if the passed
verb
is POST but nodata
object is passed, the Promise rejects with a meaningful error message.Hint: make the error object of type
{ error: string }
to conform to the error types returned by the API.Our
makeRequest()
function also fails to check if theverb
it receives is valid — remember, we only want GET or POST requests. Let's add a check for that too. Create a check which will reject the Promise with a meaningful error if theverb
argument is anything other than'GET'
or'POST'
. Right! OurmakeRequest()
function is now much more robust.Let's move on to
createPost()
. In previous chapters, you saw that we can usetry
andcatch
to handle async/await errors. In fact, we haven't handled a single error increatePost()
yet! Let's solve that now. IncreatePost()
, we make three parallel calls, followed by one single call which relies on the success of the previous three. Therefore, weneed something along the lines of:
try { // three calls try { // final call } catch (error) { // handle final call error } catch (error) { // handle three calls errors }
Remembering that the
error
that is thrown bymakeRequest()
is of type{ error: string }
, set uptry...catch
blocks that follow the above structure and handle errors correctly.
Once you've given it a go, watch me code a solution and see how your approach compares:
Just these few seemingly simple steps can make a real difference between buggy, hard-to-maintain code and a robust, maintainable, reliable app. Writing code with error handling in mind will also make you write better code, as you will constantly be wondering about what could go wrong and preemptively avoiding unnecessary errors.
Let's recap!
In this chapter, we had a look at how to approach error handling. In particular, we saw:
how to use simple checks to prevent functions from failing,
how to use try...catch blocks with nested async/await to handle errors gracefully.