Lesson Weekend

In an ideal world, all our code would function perfectly and never have errors. But in the real world errors are a regular occurrence. By this point, you’ve learned how to debug applications with console.log() and debugger. You’ve also learned to lint and to continuously test your codebases with Jasmine and Karma. Let’s take this knowledge one step further and learn about exception handling.

First, what is an exception? An exception is an unusual problem that arises in your code. An exception should be just that: exceptional. What does that mean? Exceptions should handle unexpected errors in our code, not anticipated errors. When a user enters their password incorrectly, that’s an anticipated error. Users often make mistakes, so we shouldn’t throw exceptions when they do.

However, let’s say we have a complex application that handles credit card payments. What would happen if we had a payBalance() function that accidentally charged our customers twice? That would be a serious and unexpected error.

Programmers use exception handling to deal with serious and unexpected anomalies in their code. Exception handling is a feature of programming languages in general, not just JavaScript. In JavaScript, we can handle an exception with a try...catch block, which looks like this:

try {
  //Code to try goes here.
} catch {
  //Log any raised errors.
}

We can wrap any code inside the try block. Then, if that code has errors, control will shift to the catch block, where we can write code to handle these errors.

We can’t use a try block by itself; doing so will throw an error. try blocks must always be accompanied by either catch, finally, or both. We won’t cover finally in depth, other than the fact that a finally block always runs regardless of whether the try block has errors that are caught. finally blocks are often used for cleanup or freeing up resources.

Let’s create a very basic application to demonstrate how to use try...catch blocks. The application asks a user to input a number; if the number is negative, the application will throw and catch an error. Before we start, it’s important to note that this is not a situation where we’d use exception handling; after all, we expect users to make mistakes. However, we can use this example to show how exception handling works.

The root directory of our application will have two files:try.html and try.js. Here’s the HTML:

try.html
<html>
  <head>
    <script
  src="http://code.jquery.com/jquery-3.2.1.min.js"
  integrity="sha256-hwg4gsxgFZhOsEEamdOYGBf13FyQuiTwlAQgxVSNgt4="
  crossorigin="anonymous"></script>
    <script type="text/javascript" src="try.js"></script>
    <title>Enter a positive number</title>
  </head>
  <body>
    <div class="container">
      <h1>Please enter a whole number above 0</h1>
      <label for="number">Enter your number:</label>
      <input id="number" type="text">
      <button class="btn-success" id="submittedNumber">Is your number valid?</button>
      <div id="displayNumber"></div>
    </div>
  </body>
</html>

Now let’s take a look at the JavaScript code:

try.js
$(document).ready(function() {
  $('#submittedNumber').click(function() {
    const inputtedNumber = parseInt($('#number').val());
    $('#number').val("");

    function checkNumber(number) {
      if (isNaN(number) || number < 0) {
        return new Error("Not a valid number!");
      } else {
        return true;
      }
    }

    try {
      const isNumberValid = checkNumber(inputtedNumber);
      if (isNumberValid instanceof Error) {
        console.error(isNumberValid.message);
        throw RangeError("Not a valid number!");
      } else {
        console.log("Try was successful, so no need to catch!");
        $('#displayNumber').text("This number is valid. You may continue.")
      }
    } catch(error) {
      console.error(`Red alert! We have an error: ${error.message}`)
    }
  });
});

We’ll skip the jQuery and jump right into the new concepts. First, we have a checkNumber() function which will check to see if the number is NaN or below 0. If it is, it will return an Error.

An Error is a built-in JavaScript object. There are a number of different types of errors that we could specify; for instance, instead of creating a new Error object, we could create a new RangeError. In fact, a RangeError would make more sense here because it’s more specific. The documentation for JavaScript’s Error object states that a RangeError represents “an error that occurs when a numeric variable or parameter is outside of its valid range.” That’s exactly the case here.

We also pass a string value into the Error object: return new Error("Not a valid number!"). We should always pass a value into any Error objects we create; when the error is raised, we can see this value by looking at its message property. Because errors can be very difficult to pinpoint in a larger application, the added detail is essential for debugging.

Next, we have our try...catch block. We start with a conditional: if the result of our checkNumber() function is an instanceof Error, our application will throw an error.

We have a new JavaScript operator here: instanceof. instanceof is specifically used to check the type of a JavaScript object. (It does this by looking at the prototype chain of the object, which is a topic beyond the scope of this lesson.) We can test it out in the console:

let error = new Error;
let error2 = new RangeError;
error instanceof Error;
error2 instanceof Error;

Both of the last two expressions return true. Note that while error2 is a rangeError, it’s also an Error as well.

You may have stumbled across console.error before, but if you haven’t, it operates in a similar fashion to console.log. The only difference is that the message is outlined in red. (There’s also console.warn, which is generally used for notifications about deprecated functionality.)

Inside our console.error message, we log the error.message. If we hadn’t passed a string into our Error object before, the message property would be undefined and we’d be depriving ourselves of useful information for debugging.

Next, we throw a RangeError to ensure that control moves to the catch block. (We could have thrown a basic Error as well; we use RangeError to demonstrate how we could be more specific here.) throw allows developers to define exceptions in an application. For instance, JavaScript itself doesn’t care if we call payBalance() twice, charging our customers double in the process. To actually catch that behavior, we’d need to write and throw a custom exception.

If we wanted, we could throw an error outside of a try...catch block. If we were to do so, the program will terminate. That’s not really what we want, however; instead, our application should be able to handle the error without terminating (unless it’s absolutely necessary to terminate).

Our catch block could handle exceptions in a number of ways. The most obvious (and passive) is to log the error. However, since control has moved to the catch block, we could technically run any code we want, including code that allows us to handle the error gracefully without terminating.

It’s very simple to incorporate try...catch blocks. In fact, it’s easy enough that it can be very tempting to start using these blocks to handle many errors, including unexceptional ones. However, this is a mistake for a number of reasons. When developers see a try...catch block, they will assume it’s for handling serious exceptions. Using try...catch blocks in other cases can be confusing. For instance, why would we use try...catch for user input when validations are used for that exact purpose?

try...catch can also result in a performance hit. While this usually won’t be an issue, it’s important to consider, particularly when an application has a long and resource-intensive backtrace.

Just as importantly, our code would become both unreadable and very painful to write if we wrapped everything in a try...catch block. Think about going through the scanner at the airport; you only need to do that once, when you’re going into the terminal, not every time you go to the bathroom!

While the basics of exception handling are relatively easy, knowing when to use exception handling is a more advanced concept that comes with practice. During this week’s Monday and Tuesday project, consider situations where your application might have serious exceptions. Here’s an example. In the game we created in the last lesson, it should be game over if the bear eats you. However, what if there were certain unforeseen circumstances where you could continue to play even after you’ve been eaten? This would be a serious exception. While we can’t necessarily anticipate how this issue would arise, we could wrap part of our code in a try...catch block to see if and when it does.

You now have a new tool to catch and handle errors. It may not be clear just yet when and how often you should wrap your code in try...catch blocks, but it will become clearer with time. In the meanwhile, make sure you implement at least one try...catch block in your next project!