Coder Social home page Coder Social logo

custom-error's People

Contributors

j-prototype avatar j-u-p-iter avatar

Watchers

 avatar  avatar

custom-error's Issues

Custom Error

Error object is a simple object, that provides information about an error happened in any part of the code.

In JavaScript there's main Error class to create such type of objects:

const error = new Error('some error message');

There're tons of different reasons when errors can happen:

  • you're using incorrect identifier name and as result get ReferenceError. Let's say you've decided to create an error instance in the code, but instead of new Error('some error happened') you've written new Errr('some error happened'). As result, you're trying to reference incorrect variable in your code "Errr" and get ReferenceError.
    -you're using incorrect type for the method's argument and as result get TypeError. Let's say the method processNumber() expects to get some number as an argument to process, but instead it gets a string as an input. In such type of situations it would be correct to check the type of the argument, passed to the method, and in case of wrong argument's type throw the TypeError.
  • the request you sent to the server wasn't successfully resolved and you get HTTPError. Let's say you're sending HTTP request to the server and instead of getting 200 successful response you get 400 error response. In such type of case it would be correct to throw the HTTPError or even better BadRequestError checking the status of the response;
  • before sending some form data to the server you're validating it and some of the properties are messing and some has incorrect type. In this case it's better to validate the data sending to the server and to throw a ValidationError.
    And I can give you a lot such type of examples of situations when we need to throw an error.

The errors can be thrown by many different parts of the system:

  • by developer in the application itself;
  • by third party code;
  • by runtime (NodeJS, browser)

In JavaScript from the very beginning we have Error class to create instances of all types of errors. And majority of applications and tools are using only this class to create error's instances. JavaScript since those early days introduced couple new error classes based on original Error class (inheriting from it). Currently there are: EvalError, InternalError, RangeError, ReferenceError, SyntaxError, TypeError, URIError.

The most interesting errors among the provided above are:

  • RangeError - creates an instance representing an error that occurs when a numeric variable or parameter is outside of its valid range;
  • ReferenceError. Object represents an error when a non-existent variable is referenced. This error happens often during runtime. This is why it's so useful to know about it. Every time you see such an error while running your code it means, that there's something wrong with naming of at least one of the variables/functions used in your code.
  • TypeError. Creates an instance representing an error that occurs when a variable or parameter has not of a valid type.

Instances of all of these errors are described by three main properties:

  • message. The argument you pass to the constructor, creating an error instance: new Error('Some error message').
  • name. The name of the error, set up by constructor under the hood and normally equals to the constructor's name.
  • stack. History (call stack) of files that are responsible of causing the Error. Also it includes the error message at the top. So, one more time, the stack consists of an error message, which is followed by stack trace. The stack trace starts with the most recent point, the closest to the happened error and goes down till the most outward "responsible" file.

IMO the error classes provided by default for developers are not enough to handle errors properly. And there're two main problems with that:

  • the thin set of predefined error classes. There're a lot of types of errors in JavaScript we don't have an appropriate class for.
  • the main set of default properties is not enough to fully describe and handle an error.

Why is this so important to have specific error classes? Good question! Specific error classes allow us in a very concise and readable form to point out the main problem, happened in you code. When you see "ReferenceError" instead of common "Error" before an error message you can easily detect the main reason of an error without even looking at the error message - there's something wrong with one of the variable names in your code. In other words we can say, that specific error class name is a very good title, introducing the main information about error in you app. The easier and faster it is to detect the reason of an error the faster and easier to debug and fix the issue. So, specific error classes fasten development process, which is definitely a good thing.

Why is it important to have a wide set of properties, describing the error? As it was said the standard set of properties we use to describe the error is: name, message, stack. While these properties are very important to have in any error object in some cases it's just not enough of them to detect the reason of an issue programmatically. The best example I could provide here is the case of HTTPError. Let's say we have an error from the server, that has name, message:

  • name = "HTTPError";
  • message = "The property name can't be empty".
    The main question here - is this error object enough to detect the reason of an error on the client side to make a decision what to do next?The obvious answer is no, it's not enough.

The first problem is that it's just anti-pattern to use error messages to detect the reason of an error:

if (errorMessage === The property name can't be empty'') {
  // the name is empty
}

We don't know two main characteristics about the provided above error:

  • what exactly type it has;
  • what exactly wrong.

The solution would be to expand the standard set of error properties with three additional properties:

  • status - the http status of failed http response. In our case it should be 400. It will show us a clear picture, that there's something wrong with the structure of our response. But it's still not enough to detect what is exactly wrong.
  • type - the type of an error. It can sound like "propertyIsRequired". This property gives us information, that we haven't sent required property to the server. Again this information is still not enough to get exact answer what exactly wrong with the request dat.
  • property - the name of the required property that hasn't been sent to the server.

All these properties play one common role - give us full picture of what exactly is wrong with the request. Without any of these properties the information is not full and to work with such type of non-verbose errors is pretty hard. Sometimes it's even almost impossible to implement correct errors handling functionality without such type of reasonably verbose errors.

Somebody can say that Error class already gives us enough flexibility
And somebody can say, that JavaScript provides the freedom to create any error class with any set of properties you need. And I agree. We can easily create any custom error class and set up any properties we need to have there. But the problem with this is that IMO when you have too much freedom it is also a bad thing. With such amount of freedom two different developers will implement HTTPError class differently - with different additional properties and/or methods. Both of them could be more or less correct and it's a good thing. But the bad thing is that they will be inconsistent. IMO to have one common set of rules is much more better, than neverending freedom, when everybody does what they want. When we have rules - we have convention over configuration. We now, that if an error is called HTTPError it has some well known by everybody set of properties and methods. And, the same with any another custom error.

One more benefit of creating custom errors is that simplifies the way how we can handle them.

Speaking about handling of errors there're three things to understand before we continue:

  1. We should throw errors, but not return them. The benefit of this approach is that being thrown it bubbles up through the call stack and can be caught on any execution level that can be considered as an appropriate to handle it.
  2. We should handle the errors we know how to handle in the place the most appropriate the handle them. We should throw unknown errors we don't expect to get and as result we don't know how to handle.
  3. Handling errors mean to process them. And, there're different possible ways to do it: to log them out in the console, to log them into the terminal, to send email with an error report, to send them to some third party service - centralised place to collect errors in our application to log and to analyse them. And, what is important as well - different kinds of errors could be handled differently. Some of them you just log into the console, some of them you log into the file and send error message about it and etc.

So, knowing that, we can do a short resume, that handling errors is the process of throwing them in one place, catching them in another appropriate place and processing them differently depending on the type of an error.

Given that we come to the conclusion, that we need to find the way to differentiate different types of errors. And if we create for each type of an error different type - we are able to distinguish them and as result to process them individually. The pseudo code for such type of differentiating process can look like this pseudo code:

if (err instanceof CustomError) {
  if (err instanceof ValidationError) {
     // handle ValidationError
  }

  if (err intanceof SyntaxError) {
    // handle SyntaxError
  }
}

Let's provide simple but very self-descriptive example of why we should create custom errors in our code.

Let's say we're building NodeJS application, using Express framework. And, instead of handling each possible error by place in controller's handlers we can do it in one common place - common middleware, responsible for error handling. But, we get this opportunity only if we can customise an error object somehow to be able to easily distinguish one error from another. This is how it would look like in this case:

if (err instanceof AuthenticationError) {
    return res.status(401).send('not authenticated');
}

if (err instanceof UnauthorizedError) {
    return res.status(403).send('forbidden');
}

if (err instanceof InvalidInputError) {
    return res.status(400).send('bad request');   
}

if (err instanceof DuplicateKeyError) {
    return res.status(409).send('conflict. entity already exists');
}

// Generic error
return res.status(500).send('internal error occurred')

Converting generic error objects into specific error objects is especially very important if your application needs to take different decisions based on the type of an error. In our case it helped to reduce duplication logic of handling errors and put it into one common middleware. As result it saves our time and makes an application more reliable, less error prone.

Taking into account all this stuff I decided to create @j.u.p.iter/custom-error package, that will contain set of custom errors with predefined interface I could use consistently among of my projects. In one of my next articles I'll tell you more about this package, what custom errors it contains for now and what's more importantly what principles I put into creating each of them.

Conclusion.
It's very important to create custom errors with predefined set of properties enough to handle the error properly. It has a lot of benefits like:

  • increasing development process;
  • improving readability of the errors;
  • making it easier and to handle errors properly. And, in some cases this is the only possible way to handle them properly.
    Thank you for the reading and see you next time.

Custom Error

The goal of the @j.u.p.iter/custom-error is to structurise the process of creation of custom errors for all my personal projects.

It does make a lot of sense why it's so useful to have such type of tool:

  • the easier and as result faster to debug code;
  • the faster development process;
  • the much more readable errors;
  • the much easier to handle errors. And sometimes it's the only possible way to do it.

Wow! So many benefits, but still no proof for the moment. Ok, let me prove it, using one simple example.

The main advantage that custom errors give us is an ability to recognise errors programatically. What I mean is that developers should be able to get an error object and to detect the type and the nature of an error, checking properties of this object.

Let's say we get a HTTP error from the server with 400 status. The error has only one property "message" and it sounds like "Bad request data". Our goal is to handle an error that comes in HTTP response properly on a client side.

The thing is the error message and the response status is not enough to handle an error properly. Actually, the only way this message can be used on the client side is to observe the message in the Network's tab in the developer's console. It can't be used programatically to detect the type of an error. It's just anti pattern to do it. It can't be shown for the end user neither, because such type of messages should be very easy to change, and if they are located on the server the update of these messages can take a lot of time. The status is also is not enough, cause it tells only that something is wrong with the request body and nothing else. What exactly is wrong we can't say and as result we can't use it to handle error properly as well.

So, we need to find the way to extend the error's object somehow to clarify the reason of an error.

With help of this object we should be able to detect what exactly is wrong with the request's body.

Let's say we ended up create a special error class for the error - PropertyIsRequiredError. The JSON representation, that will be sent from the server to the client looks like that:

error: {
  message: 'Name field is required',
  type: 'propertyIsRequired',
  property: 'name',
  context: 'UsersService',
}

As we can see here we have more than enough information to detect fully the reason of an error programatically on the client side.

We have a clear error message, that we can observe in the developer's console Network's tab. type property allows us to programatically check the reason of an error and the property name allows us to clarify the reason even more. With all this information we have everything we need to handle an error.

So, according to the provided example we definitely should have custom errors in our code and use them intensively.

The package consists of multiple parts:

The core custom error class other classes inherit from: CustomError class.

This class inherits from native JavaScript Error class. There're three reasons why we inherit from the Error class:

  • it provides access to the very useful stack property;
  • it allows us to detect all Errors in the code by using condition if (err instanceof Error) { ... };
  • it's just correct and clean way of doing things in the OOP world.

The important thing to understand for the moment is that all other custom classes inherit from CustomError class. And this is the mandatory to create each new custom error class inherited from CustomError.

Under the hood CustomError class does multiple things:

  • inherits from Error class as it was already said;
  • sets up date public property which stores the creational date of an error like public date = new Date().
  • sets up context public property which is provided from children classes specifically. This property contains the place the error has happened. Let's say the error raised in the UserService. It's very important to mark the original place-source of the issue to understand where the issue comes from. stack property provides enough info for that already but the context property makes this identifier more readable. And also, it could be helpful for logging purposes.

Based on CustomError there are several categories of errors introduced by package:

  • http errors;
  • syntax errors;
  • type errors;
  • typescript errors;
  • validation errors;

The set of these categories and the set of errors in each category is not full and will be constantly updated through the time.

Each category of errors has one base class and a lot of child classes inheriting from this base class. For example, http errors category of errors consists of HTTPError base error class and two subclasses of HTTPError - BadRequestError and CommonError. So, let's stick to this terminology for better understanding of further documentation:

  • base class - the main class for each category of errors other classes of this/that category inherits from. It can't be used directly. Well, technically can, but shouldn't be. All base classes inherit from CustomError class.
  • subclasses - the classes that inherit from base error class and respectively from CustomError class.
    Each subclass is described by its own public interface. The properties names and their values are different for each subclass. However they have several common properties:
  • name. The string representation of the name of the error. It always duplicates the subclass name.
  • date. The date the error was created.
  • context. The place in the code the error was created.

Let's look at each category of errors introduced by package:

http errors

This is very specific and at the same time widely used category of errors.

This is the only category, that has json representation, because we need to send them from server to the client.
So, under the hood for each http error subclass the toJSON method is declared. The set of properties json representation includes depends on the type of the HTTP error. However all json representations contains three common main properties:

  • context. Contains the name of the place in the code the error has happened.
  • message. The standard error message
  • type. Type of an error, that declares what error was exactly detected on the server.

More about type property.
There's a very popular way to mark the type of an error, that is sent from the server to the client - with help of an error code. As an example you can look at the Facebook documentation documentation about code errors they use. The similar information you can find about Google's errors codes. We name it as a type instead of code but the meaning we put into this is absolutely the same.
The purpose of the error type is clear - to extend information about the error and to make it possible to handle this error on the client side programatically.
There're different types of standards different teams choose for the error codes. Some teams, as, for an example, the Facebook team, use numeric types of codes. And it seems, this is the most popular format for code errors. But we can use words to describe errors codes. This is what Google does. They use words in camelCase to describe the "reason" of an error. We made a decision also to use camelCased words. In my opinion such type of code is more descriptive. It's easier to detect exactly the reason of an error without using any additional dictionaries.

As an example of such of types: requiredProperty, requiredURLParam, invalidEmail and etc.

As we can this types are self-explanatory if they are written by words without any additional dictionaries with the translation of error codes to their meaning.

HTTPError
This is the base class for all http errors.

BadRequestError
This is subclass of HTTPError.
It's described by next public interface:

  • name property equals to "BadRequestError".
  • code property equals to 400.

JSON representation:

{
  error: {
    message: this.message,
    type: 'badRequestError',
    context: this.context
  }
}

This type of error doesn't provide information about what exactly wrong with the request's data. And, sometimes, from security point of view it makes sense to do not to help bad users to unlock account that doesn't belong to them, for example.

Create example of using CustomError class

We have getUser method on the client, that fetches a user data from the server.

Using this article https://javascript.info/custom-errors, provide in the documentation your own example of creating custom error classes:

class CustomError extends Error {}

class ValidationError extends CustomError {}
// hint. In the service we detect ValidationError. During handling (in ErrorHandler) we create additionally HTTPError and send it to the client.
class HTTPError extends CustomError {}

Question - what validation error messages should you send to the client to be able to process them properly on the client side:

  • what property is required;
  • incorrect format of data;
  • need to fill in captcha.

One of the solutions can be special code field (different from the statusCode). It contains universal code, according to some standarts, that is clear for the client.

As an example: https://codeburst.io/error-handling-in-spa-applications-e94c4ecebd86
https://www.baeldung.com/rest-api-error-handling-best-practices

https://levelup.gitconnected.com/the-definite-guide-to-handling-errors-gracefully-in-javascript-58424d9c60e6

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    ๐Ÿ–– Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. ๐Ÿ“Š๐Ÿ“ˆ๐ŸŽ‰

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google โค๏ธ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.