TOC
Error handling:

Error types & conditional catching

In the previous articles in this chapter, we talked about how we can catch errors in JavaScript, using try..catch and try..catch..finally blocks. We even talked about how we could throw errors ourselves.

Until now, we have dealt with the generic Error type, which errors in JavaScript are based on, but actually, there are more specific versions of a JavaScript error. This becomes especially relevant when you want to distinguish between these different types of errors, which we'll discuss in this article. We'll also discuss how you can create your own error types, for throwing specific errors to match the type of problem.

Error types

The generic Error type will always provide you with at least a name and a message, describing the problem. However, based on the error that occurred, JavaScript might use a more specific error type, based on the generic Error type, but possibly with more properties and/or methods.

For a full list of the currently available error types, please see this reference page. For now, let me show you one of the specific error types that JavaScript might throw: The RangeError. It can occur in many different situations, but one example would be if you try to divide a BigInt with zero, like this:

let bigInt = BigInt(10000000000042);
let zero = BigInt(0);
alert(bigInt / zero);

If you run the example, you will see an error like this in your console:

Uncaught RangeError: Division by zero

As you can see, this is now a RangeError instead of just the generic Error type. We can verify this when we catch the error, and we can even use it to distinguish between other errors.

Conditional catching

By utilizing a technique called conditional catching, we can actually vary our course of action depending on which type of error is thrown. This can be very useful when dealing with more complex cases, where several different types of errors might occur.

We know which type of error is thrown either by looking at its name property, which will match the type of error, or by comparing it to the specific type using the instanceof operator. Here's an example of how conditional catching might look:

try
{
	let bigInt = BigInt(10000000000042);
	let zero = BigInt(0);
	alert(bigInt / zero);
}
catch(error)
{
	if(error instanceof RangeError)
		alert("Something is wrong with the range!");
	else
		alert("Something else went wrong...");
}

Notice how I can easily differentiate between the action I want to perform, based on the type of error that is thrown.

Re-throwing errors

In the last article, I demonstrated how you can throw an error to give the caller of your code a precise indication of what went wrong. But sometimes you only want to handle a specific type of error and let other types be handled by the consumer of your code/function.

For situations like that, we can combine the technique of conditional catching with the ability to throw errors from the catch statement - in other words, we can re-throw an error to pass on the responsibility of handling the error. Here's an example:

function DivideBigInts(b1, b2)
{
	try
	{				
		return b1 / b2;
	}
	catch(error)
	{
		if(error instanceof RangeError)
			alert("Something is wrong with the range!");
		else
			throw error;
	}
}

DivideBigInts(BigInt(10000000000042), BigInt(0));

In this example, I have created a function for dividing the two BigInt's. It catches any error, but in case an error occurs which isn't a RangeError, the error is simply rethrown. Of course this requires that we do some error handling of our own when we call the function.

To see the difference, in this next example, I have turned the error check around so that only RangeErrors are rethrown, and then I have added a try..catch block for the line where we call the function - this is needed because we call a function that might rethrow an exception.

I also demonstrate how you can customize the error message by re-throwing a new Error where we write our own message, mixed with the message we get from the original error thrown:

function DivideBigInts(b1, b2)
{
	try
	{				
		return b1 / b2;
	}
	catch(error)
	{
		if(error instanceof RangeError)
			throw new Error("A RangeError occurred. Message: " + error.message);
		else
			alert("Something went wrong...");
	}
}


try
{
	DivideBigInts(BigInt(10000000000042), BigInt(0));
} 
catch(error)
{
	alert(error.message);
}

If you run this example, you will now see that because our code generates a RangeError, this error is re-thrown and then handled in the last catch block, outside of the function. Here we display the error we received from the function, with our own custom error message.

Throwing custom errors

As you saw in the previous example, throwing an error with a custom message is quite easy - we can simply just instantiate a new Error object and pass along our own custom message. But sometimes this is not enough, and just like JavaScript has specialized error types for various problems, like we saw with the RangeError type, we can of course create our own type as well.

To show you how this works, I'm going to create a new type of range error which will accept a minimum and a maximum value, to give a more informative error message. We can do this by creating a class that extends the Error class, like this:

class InformativeRangeError extends Error
{
	constructor(value, min, max)
	{
		super(`Value ${value} is not within the valid range - must be between ${min} and ${max}`);
		this.name = "InformativeRangeError";
	}
}

Notice how our InformativeRangeError class accepts three parameters: The actual value, e.g. as entered by the user, as well as a minimum and a maximum value. We then use these three values to generate a much more informative error message which we pass to the original constructor of the Error class using the super keyword.

Here's a complete example where we use the class, throwing our new InformativeRangeError like it was just a regular Error:

class InformativeRangeError extends Error
{
	constructor(value, min, max)
	{
		super(`Value ${value} is not within the valid range - must be between ${min} and ${max}`);
		this.name = "InformativeRangeError";
	}
}

let min = 1;
let max = 10;
let number = Number(prompt("Please enter a number:"));
if(number >= min && number <= max)
	alert("Thank you!");
else
	throw new InformativeRangeError(number, min, max);

If you run this example, try entering a value outside of our desired range (1-10), e.g. 14 - if you look in your console, you will see our custom error message:

Uncaught InformativeRangeError: Value 14 is not within the valid range - must be between 1 and 10

Summary

JavaScript comes with different types of errors for different types of problems, and if they're not enough, you can easily build your own error type. We can use these different types, both built-in and custom, to differentiate between what we do for various types of problems by using conditional catching, and (optionally) re-throwing the errors we don't want to handle in the place where they occur.