Have you ever made a mistake in your code that could not be executed afterward? Or have you sent a request to a server that failed and led to some unexpected consequences? Of course you have — like so many of us.
The try block contains a set of statements where an exception can occur, and is always followed by a catch block, which handles the exception that occurs in the associated try block. The catch block “catches” an error so that you can get more information about it by checking the properties of the error object.
In addition to the try/catch construction, the “throw” keyword is also widely used in this context. By applying it, we can create our own customized errors. This makes our application and business logic more powerful, understandable, and, I’d say, human-readable.
The first real-world example off the top of my head is data validation:
In the code snippet, we’re trying to retrieve the token from the query property of the request. If there’s no token, or the value is empty, we’re literally “throwing” an error, which is caught by the catch block and shown in the logs. Our exploration doesn’t stop here.
Since Error is a class, it's obviously possible to create our own error classes for the sake of clarity. Check it out:
In this example, we’ve created our own ValidationError class and used it for error handling of the specific business logic above. In the logs, it comes up as “Failed to authenticate ValidationError. Have not received [token]”. Applying this technique, you can determine the error type and handle it differently:
Rethrowing an error
The last technique I’ll mention here is to rethrow an error. Use it when you need to handle an error on the upper stage, not inside a particular handler.
There’s a de facto standard according to which the error handler (or a particular catch block) should handle the error specifically related to it, while everything else should be rethrown further. In the end, there should be a default error handler that handles everything that was not caught by our gatekeepers (catch blocks):
Now, let’s refactor our code a bit. It’s better to move the validation code to a separate function for readability purposes:
Using the finally block
Sometimes, it might be required in cases of try/catch and success/fail that there be some finalizing and independent action. The basic use case is as follows: a code opens a file descriptor; reads/processes data inside it; and, in case of an error or a successful completion, the file should be closed and resources released.
Let’s assume that the authenticate function is async:
So, if the promise is rejected, the catch block will handle it in the same way as above.