facebook google plus twitter
Webucator's Free Advanced JavaScript Programming Tutorial

Lesson: Errors and Exceptions

Welcome to our free Advanced JavaScript Programming tutorial. This tutorial is based on Webucator's Advanced JavaScript Programming course.

JavaScript provides several methods for catching and handing errors, the most useful of which is try/catch/finally.

Lesson Goals

  • Learn how to use JavaScript's try/catch/finally to handle errors.

Runtime Errors

Web browsers are such a hostile environment that it is almost guaranteed that we will constantly deal with runtime errors. Users provide invalid input in ways you didn't think of. New browser versions change their behavior. Ajax calls can fail for any number of reasons.

Many times we cannot prevent runtime errors from happening, but at least we can deal with them in a manner that makes the user experience less traumatic.

Completely unhandled errors

Look at this seemingly trivial code sample:

Code Sample:

ErrorsExceptions/Demos/simple-bug.html
---- C O D E   O M I T T E D ----

function getInput() {
	var name = prompt('Type your name', '');
	alert('Your name has ' + name.length + ' letters.');
}
getInput();
---- C O D E   O M I T T E D ----

Code Explanation

It may not be obvious, but this code has a bug waiting to break free. If the user clicks Cancel or presses Esc the prompt() function will return null, which will cause the next line to fail with a null reference error.

If you as a programmer don't take any step to deal with this error, it will simply be delivered directly to the end user, in the form of a utterly useless browser error message like the one below: Error dialog

Depending on the user's browser or settings, the error message may be suppressed and only an inconspicuous icon shows up in the status bar. This can be worse than the error message, leaving users thinking the application is unresponsive.

Globally Handled Errors

The window object has an event called error for which we can add an event handler, listening for global errors. The next demo shows an example of this:

Code Sample:

ErrorsExceptions/Demos/simple-bug-onerror.html
---- C O D E   O M I T T E D ----

window.addEventListener("error", function (e) {
    alert('Error: ' + e.error.message);
	return true;
});

function getInput() {
	var name = prompt('Type your name', '');
	alert('Your name has ' + name.length + ' letters.');
}
getInput();
---- C O D E   O M I T T E D ----

Code Explanation

If the user presses the "escape" key when the prompt asks for a name, our event handler fires.

Here's the alert that we shows to the user: Error message

Structured Error Handling

The best way to deal with errors is to detect them as close as possible to where they occur. This will increase the chance that we know what to do with the error. To that effect JavaScript implements structured error handling, via the try...catch...finally block, also present in many other languages:

Syntax

try {
    statements;
} catch (error) {
    statements;
} finally {
    statements;
}

The idea is simple. If anything goes wrong in the statements that are inside the try block's statements then the statements in the catch block will be executed and the error will be passed in the error variable. The finally block is optional and, if present, is always executed last, whether or not an error is caught.

Let's fix our example to catch that error:

Code Sample:

ErrorsExceptions/Demos/simple-bug-try-catch.html
---- C O D E   O M I T T E D ----

window.addEventListener("error", function (e) {
    alert('Error: ' + e.error.message);
	return true;
});

function getInput() {
	try {
		var name = window.prompt('Type your name', '');
		alert('Your name has ' + name.length + ' letters.');			
	} catch (error) {
		alert('The error was: ' + error.name + 
		'\n The error message was: ' + error.message);
	} finally {
		//do cleanup
	}
}
getInput();
---- C O D E   O M I T T E D ----

Code Explanation

The error object has two important properties: name and message. The message property contains the same error message that we have seen before. The name property contains the kind of error that happened and we can use that to decide if we know what to do with that error.

With that in place, if we reload the page and cancel out of the prompt, we will get the following alert: Error message

It's a good programming practice to only handle the error on the spot if you are certain of what it is and if you actually have a way to take care of it (other than just suppressing it altogether.) To better target our error handling code, we will change it to only handle errors named "TypeError", which is the error name that we have identified for this bug.

Code Sample:

ErrorsExceptions/Demos/simple-bug-try-catch-specific.html
---- C O D E   O M I T T E D ----

window.addEventListener("error", function (e) {
    alert('Error: ' + e.error.message);
	return true;
});

function getInput() {
	try {
		var name = window.prompt('Type your name', '');
		alert('Your name has ' + name.length + ' letters.');			
	} catch (error) {
		if (error.name == 'TypeError') {
			alert('Please try again.');
			getInput();
		} else	{
			throw error;
		}
	} finally {
		//do cleanup
	}
}
getInput();
---- C O D E   O M I T T E D ----

Code Explanation

Now if a different error happens, which is admittedly unlikely in this simple example, that error will not be handled. The throw statement will forward the error as if we never had this try...catch...finally block. It is said that the error will bubble up.

Throwing custom errors

We can use the throw statement to throw our own types of errors. The only recommendation is that our error object also has a name and message properties to be consistent with the built-in error handling.

throw {
	name: 'InvalidColorError',
	message: 'The given color is not a valid color value.'
};

Nested Try/Catch

We can nest try/catch/finally blocks to an arbitrary depth, depending on the need of our particular page or application. Consider the following example:

Code Sample:

ErrorsExceptions/Demos/nested-try-catch.html
---- C O D E   O M I T T E D ----

try {
    document.write("Outer try...<br/>");

    try {
        document.write("Inner try...<br/>");
        throw new Error("custom error");
    }
    catch (e) {
        document.write ("Inner catch caught error: " + e.message + "<br/>");
        throw e;
    }
    finally {
        document.write ("Inner finally...<br/>");
    }
} catch (e) {
    document.write ("Outer catch caught error: " + e.message + "<br/>");
} finally {
    document.write ("Outer finally...");
}

---- C O D E   O M I T T E D ----

Code Explanation

Our code contains an outer try/catch/finally block and an inner try/catch/finally block. We write to the screen the progression of the flow.

In the inner try we throw a custom error; the error is caught by the inner catch, after which the inner finally executes. The error propagates to, and is caught by, the outer catch, after which (finally) the outer finally executes.

We'll ask you to try out (no pun intended) error handing in the next exercise:

Try/Catch/Finally

Duration: 10 to 15 minutes.

In this exercise, you will handle potentially-problematic user input in a simple calculator, designed to return the quotient of two user-entered numbers.

  1. Open ErrorsException/Exercises/trycatch.html for editing.
  2. The result of the user clicking the "=" button is to write to the #msg div one of the following:
    • "both operands must be a number" if either the dividend or divisor (or both) is not a number or is blank;
    • "cannot divide by zero" if the divisor == 0; or
    • the answer, if both dividend and divisor are valid numbers.
    and to clear the fields of entered values.
  3. Use try/catch/finally for error handling.
  4. Test your solution in a browser.

Solution:

ErrorsExceptions/Solutions/trycatch.html
<!DOCTYPE HTML>
<html>
<head>
<meta charset="UTF-8">
<title>Try/Catch</title>
<style>
    input.operator {
        width:30px;
    }
    input#quotient {
        width:100px;
    }
</style>
<script type="text/javascript">
    function DisplayAnswer() {
        var dividendField = document.getElementById('dividend');
        var divisorField = document.getElementById('divisor');
        var msgField = document.getElementById('msg');

        var dividend = dividendField.value;
        var divisor = divisorField.value;
        var quotient;

        try {
            if (isNaN(dividend) || isNaN(divisor) || dividend=='' || divisor=='') {
                throw new Error("both operands must be a number");
            } else if (+divisor == 0) {
                throw new Error("cannot divide by zero");
            } else {
                quotient = dividend / divisor;
                msg.innerHTML = dividend  + " / " + divisor + " = " + quotient;
            }
        }
        catch (e) {
            msg.innerHTML = 'There was a problem: ' + e.message;
        }
        finally {
            dividendField.value = '';
            divisorField.value = '';
        }
    }

    window.onload = function() {
        var equals = document.getElementById('equals');
        equals.addEventListener('click', DisplayAnswer, false)
    };

</script>
</head>
<body>
<input type="text" id="dividend" class="operator"> / <input type="text" id="divisor" class="operator"> <button id="equals">=</button>
<div id="msg"></div>
</body>
</html>

Code Explanation

In the try block we check for not-a-number-ness for the two user-entered values. If valid, we then check to make sure that the divisor is not zero. If either test fails, we throw a custom error. If neither test fails, then we write the answer to the #msg div.

If an error occurred, then our catch block writes the error to the #msg div.

We use the finally block to clear the user-input fields.