TOC
Error handling:

Catching & throwing errors (try/catch/finally)

In the previous article, we introduced error handling using try..catch blocks. In this article, we'll dig a bit deeper and discuss more available tools for dealing with errors/exceptions.

try..catch..finally

First of all, allow me to introduce a third part of the try/catch construction: The finally-part. As we talked about in the previous article, we have the try-part, which contains the code that might throw an error. Then we have the catch-part, which will be executed IF, and only if, the try-part results in an error. But we also have one more, optional, part, called finally.

The finally-part is for the code you want to have executed after the two other parts, no matter if an error/exception occurs or not. Generally speaking, the finally-part is often used to clean up after something we (tried to) do in the try-part. So for instance, if you open a connection to a database or open a file for writing, and something goes wrong, you may use the finally-part to ensure that the connection/file is closed before proceeding with the rest of your code.

Here's an example of how a try..catch..finally block can be used:

try
{
	openDBConnection();
	doStuffThatMightThrowAnError();
}
catch(error)
{
	handleError(error);
}
finally
{
	closeDBConnection();
}

In this example, if anything goes wrong in the try-part, or if everything works as expected, the connection to the database is closed. To see the true power of the finally-part, consider this example:

function DoStuff()
{
	try
	{
		return 1;
	}
	catch(error)
	{
		return 2;
	}
	finally
	{
		return 3;
	}
}

alert(DoStuff());

Since the code in the try-part is not throwing an error, you might expect that the result of this function is 1, but it's not. Even though we try to return 1, the nature of the try..catch..finally construct ensures that the finally-part is executed at last, overruling the result of the function and returning 3 instead.

Throwing errors

Error handling is not solely about catching the errors. If you write code that is to be called by other programmers, you are also responsible for handling errors in your code and make sure that they are communicated to the consumer of your code in a proper manner.

As an example, consider this function:

function AddNumbers(n1, n2)
{
	if((Number.isInteger(n1)) && (Number.isInteger(n2)))
		return n1 + n2;
	return 0;
}

alert(AddNumbers("2", "40"));

In this made-up scenario, when we wrote the AddNumbers() function, it was only designed to work with integers, so we added a bit of validation to it. If any non-integer value is passed in, e.g. a floating point number or a string, as in the example above, the function simply returns 0.

This is no problem if you are both the creator and consumer of the function, as we are in the example above, but what if this specific function was buried in one of several hundred files, as part of a huge library or application, and what if the function was not a simple one, but instead 150 lines of complex logic?

When other programmers call this function, they don't know that the function requires integer parameters, and when passing in other values, they might not understand why they simply get a 0 back from it. As an alternative to this, we should consider throwing an appropriate error, to let the consumer of the function know that they used it in a wrong way:

function AddNumbers(n1, n2)
{
	if((Number.isInteger(n1)) && (Number.isInteger(n2)))
		return n1 + n2;
	else
		throw new Error("Please use only integers for parameters n1 and n2!");	
}

alert(AddNumbers("0", "42"));

Now, if non-integer values are passed to our function, we're throwing an error with an appropriate error message, explaining to the consumer of the function what went wrong. If you try to run the example, you won't see the alert - instead, you will see our custom error in the console of your browser.

We have now moved the error handling responsibility to the consumer of the function - he/she will have to catch the error and decide what should happen with it. For instance, it could be reported to the user, either with details or like a "Ooops, something went wrong!" kind of message, if we don't want to share any details with the user:

function AddNumbers(n1, n2)
{
	if((Number.isInteger(n1)) && (Number.isInteger(n2)))
		return n1 + n2;
	else
		throw new Error("Please use only integers for parameters n1 and n2!");	
}

try
{
	alert(AddNumbers("0", "42"));
}
catch(e)
{
	// Log the error
	alert("Sorry, the numbers could not be added - please try again!");	
}

Summary

The (optional) finally part of the try..catch block allows you to ensure the execution of code when the try..catch construction is exited. This code will be executed no matter if an error occurred or not, and is often used to release important resources like files or remote connections.

Using the throw keyword, we can take part in the error handling eco-system and throw errors ourselves, to give a more precise indication of what went wrong. We can even create our own custom errors, as we'll discuss in one of the next articles.


This article has been fully translated into the following languages: Is your preferred language not on the list? Click here to help us translate this article into your language!