Approaches to Determine the Error Type
Mon Mar 18 2024 03:44:06 GMT+0000 (Coordinated Universal Time)
Saved by @Marcelluki
Approaches to Determine the Error Type To correctly handle an error in an application, it's important to determine its error type. That's because there is no one-size-fits-all solution for errors when dealing with real-world applications. There are two primary approaches to determining error types in JS: Using the name of an error (stored in the name field) Using the error class with the instanceof operator First, let's talk about some general rules that must be followed regardless of the chosen approach. The main rules for error handling Here is the general approach when coding error handlers of a certain type: in the error handling block, make sure to add a condition for checking the error type using an if statement and describe the handling in the body of the if statement. Like so: ... } catch (err) { if (check_condition_for_error_1) { // Logic for handling error 1 ... return; } if (check_condition_for_error_2) { // Logic for handling error 2 ... return; } // Logic for handling unknown errors // In this example we only log them console.log(`An unknown error ${err.name} has occurred: ${err.message}`); } When coding error handlers, regardless of the approach you're using to determine the error type (check condition), adhere to these two rules: At the end of the catch block, you must describe the logic for handling unknown errors or, as often referred to, you'll need to implement "default error handling". You'll need to do this for the error to be handled in any situation, even if it doesn't meet any of the listed conditions. If there is no "default" branch and the error doesn't fall under any of the conditions (an unknown error), it will be ignored, which is unacceptable. At the end of each block that handles a specific error, add an execution stop with return to exit try...catch. We do this to avoid repeated checks in subsequent if blocks when the error has already been handled. Moreover, return doesn't allow execution to reach the "default branch". If the error has already been handled, the code execution will simply terminate earlier. Imagine what would happen if there were no return. The first error would be handled in a corresponding if block, and then once again in the handler for unknown errors. Determining the error type using its name In the JS world, it is common to use unique names for different types of errors. This allows the name of the error (the name field) to be used to determine its type. But there is a problem: for instances of standard errors of the class Error, the default error name is also equal to Error, which means we can't distinguish these errors from each other. To avoid this, engineers usually use the custom error we talked about in the previous lesson. To describe an error handler of a certain type, we can simply add a corresponding if check to the catch block. Here, the check condition will look like this: err.name === "ErrorName". Now look at the handler used with this approach: ... } catch (err) { // Check the name of the error if (err.name === "ErrorName") { // Describe the logic for handling the error ... return; } ... } We'll need to describe a separate check for each error type. This approach, using the error name, is simple and straightforward, but there are some drawbacks: The engineer needs to track the uniqueness of error names. While engineers often check that error names don't conflict within the code itself, they also need to ensure that errors don't overlap with third-party libraries. Tracking these cases can be challenging at times. It's easy to make a typo in the error name in the check block. Eventually, the check condition won't work and the error won't be processed correctly. This would happen, for example, if we misspelled SomeError as SomeEror. There is no convenient way to describe handlers for a group of errors. If you want the handler to be triggered for several types of errors, you'll need to explicitly specify each of them in the check condition. For example, if you need the handler to be triggered for the SomeError, AnotherError, and ThirdError errors, then you'll need to write the following condition: javascript err.name === "SomeError" || err.name === "AnotherErrror" || err.name === "AnotherErrror" Indeed, this is not very convenient or intuitive. Now let's look into the second way of determining if the type of error is devoid of these drawbacks: the instanceof operator. Hierarchy of errors: the instanceof operator Error classes can be inherited like any other classes in JavaScript. This allows us to describe a hierarchy of errors, group them, and describe the common handling logic. In this case, the instanceof operator is used. It allows us to determine whether the specified object (error) is an instance of a certain class, taking into account the inheritance hierarchy. For example, to check whether err is a SomeErrorName error or its special case (an inheritor class), we'd write the following: err instanceof SomeErrorName. If it is, the expression evaluates to true. Otherwise, it evaluates to false. To better understand how the instanceof operator works, let's look at an example. We'll describe three error classes: SomeErrorName,AnotherErrorName, and ChildOfAnotherErrorName. We'll also indicate that ChildOfAnotherErrorName is the inheritor (that is, a special case) of AnotherErrorName. Next, we'll check the result returned by instanceof for each of them: class SomeError extends Error {}; class AnotherError extends Error {}; class ChildOfAnotherError extends AnotherError; let someError = new SomeError("SomeError") let anotherError = new AnotherError("AnotherError") let childOfAnotherError = new ChildOfAnotherError("Child error of AnotherError") // This will return true because someError is an instance of SomeError console.log(someError instanceof SomeError) // This will return false because anotherError is an instance of AnotherError, not SomeError console.log(anotherError instanceof SomeError) // This will return true, childOfAnotherError is an instance of ChildOfAnotherError // and ChildOfAnotherError is inherited from AnotherError console.log(childOfAnotherError instanceof AnotherError) // This will return true, childOfAnotherError is an instance of ChildOfAnotherError console.log(childOfAnotherError instanceof ChildOfAnotherError) Note that although the AnotherError class was specified in the test condition, the expression returned true because childOfAnotherError inherits from AnotherError. Thanks to this operator, one handler is enough for us to handle a group of errors. instanceof checks the class, taking inheritance into account. Since a specific class is indicated in the check condition, we are insured against problems associated with the intersection of the error names and typos in the check conditions. Moreover, the IDE will prompt if there is no class with the specified name. When an inherited error requires handling logic that differs from other errors in this group, the handler is placed in a catch block above the one for the group of errors. class ErrorGroup extends Error {}; class FirstChildOfErrorGroup extends ErrorGroup; class SecondChildOfErrorGroup extends ErrorGroup; try { .... } catch( err) { // Separate handler for FirstChildOfAnotherError if (err instanceof FirstChildOfErrorGroup) { ... } // Handler for a group of errors if (err instanceof ErrorGroup) { ... } } The error hierarchy helps us organize our code by grouping errors of the same type, such as errors that would happen while working with the database, validation errors, etc. One of the most popular scenarios for using error grouping is describing API errors. In this case, instead of an endless list of conditions, the engineer just needs to describe the general class HttpError and the errors themselves (special cases); for example, DocumentNotFoundError (error 404) and ForbiddenError (error 403) should be implemented as its inheritors. For this, we can just describe one handler for HttpError. Inside this handler, we'll return an error to the user. If we need to describe an additional error, e.g. Unathorized (error 401), we won't need to change the check condition for it to be handled correctly. That's that! You've almost completed this chapter. It's time to move on to the final lesson.
Comments