Error Handling and Debugging
- That errors can be trapped and dealt with
- To use Firebug to trace and resolve errors
No matter how careful you are, it always seems that errors find their way into your code. Sometimes they are runtime errors caused by unpredicted scenarios. Sometimes the errors are just incorrect behavior of your code, popularly known as bugs.
Fortunately, we have tools to deal with either type of problem. In this lesson we will talk about detecting and handling errors and also about tracing down bugs and take them out of our applications.
Runtime Errors
Web browsers are such an 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. An AJAX call fails for a number of reasons.
Many times we can't 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 snippet.
function getInput() {
var name = window.prompt('Type your name', '');
alert('Your name has ' + name.length + ' letters.');
}
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.
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 the users thinking the application is unresponsive.
Globally handled errors
The window object has an event called onerror that is invoked whenever there's an unhandled error on the page.
window.onerror = function (message, url, lineNo) {
alert(
'Error: ' + message +
'\n Url: ' + url +
'\n Line Number: ' + lineNo);
return true;
}
As you can see, the event will pass 3 arguments to the invoked function. The first one is the actual error message. The second one is the URL of the file containing the error (useful if the error is in an external .js file.) The last argument is the line number in that file where the error happened.
Returning true tells the browser that you have taken care of the problem. If you return false instead, the browser will proceed to treat the error as unhandled, showing the error message and the status bar icon.
Here's the message box that we will be showing to the user.
Structured Error Handling
The best way to deal with errors is to detect them the closest possible to where they happen. This will increase the chances 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.
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, regardless if there was an error caught or not.
Let's fix our example to catch that error.
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
}
}
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, that's what we will see:
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.
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.');
} else {
throw error;
}
} finally {
//do cleanup
}
}
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 in error handling.
throw {
name: 'InvalidColorError',
message: 'The given color is not a valid color value.'
};
Debugging
One of the most important activities in software development is debugging. It can also be one of the most costly. That's why we need to do our best to reduce the amount of time spent in debugging.
One way to reduce this time is to create automated unit tests, which we will see in the lesson Production Grade JavaScript.
Another way is to use the best tools available and try to remove the pain associated with debugging. It used to be the case that debugging tools for JavaScript were archaic or close to non-existent. This situation has improved a lot and now we can confidently say we have feasible ways to debug JavaScript without resorting to horrendous tactics, such as sprinkling alert() calls across our code.
We won't waste your time discussing all the existing tools for debugging. Instead we will focus on the tool that singlehandedly re-wrote the JavaScript debugging history.
Firebug
Firebug is an extension for the Mozilla Firefox browser. Once installed, Firebug will turn Firefox into almost an IDE for web development.
Let's learn about Firebug's capabilities by debugging an issue in practice.
Exercise: The Background Highlighter
In this exercise we are trying to understand why our BackgroundHighlighter object is not working as expected. The object is supposed to change the background color of an input field when it gets focus and revert it when it loses focus.
For some reason the code is not working. Here is the code.
Code Sample: ErrorHandlingAndDebugging/Exercises/bgnd-changer.html
<html>
<head>
<title>Background Highlighter</title>
<style type="text/css">
.wc_debug
{
background-color:#ffc;
}
</style>
<script type="text/javascript" src="../../Libraries/DebugHelp.js" ></script>
<script type="text/javascript">
//add the debud panel at the bottom of the page
observeEvent(window, 'load', function () {insertDebugPanel();} );
</script>
<script type="text/javascript">
var BackgroundHighlighter = function (field, color) {
this.field = document.getElementById(field);
this.color = color;
this.field['onfocus'] = this.onGotFocus;
this.field['onblur'] = this.onLostFocus;
};
BackgroundHighlighter.prototype = {
onLostFocus: function () {
this.field.style.backgroundColor = '';
},
onGotFocus: function () {
this.field.style.backgroundColor = this.color;
}
};
function onPageLoad() {
//this function runs as soon as the page loads
new BackgroundHighlighter('userName', '#ff9');
new BackgroundHighlighter('company', '#ff9');
}
observeEvent(window, 'load', onPageLoad );
</script>
</head>
<body>
<form action="#">
Name: <input type="text" name="userName" value="" id="userName"/> <br/>
Company: <input type="text" name="company" value="" id="company">
</form>
</body>
</html>
- Open the above file in Firefox.
- Activate Firebug. Tools menu, Firebug, uncheck Disable Firebug (if checked) then Tools menu, Firebug, Open Firebug.
Now if you click the Name textbox, Firebug will tell you that there's an error. See the picture below and note the error message in the status bar.
The Console tab in Firebug shows that the error message is "this.field has no properties". The error problem seems to be on line number 32.
Expand the error message by clicking the "+" icon next to it. We will get one extra piece of information, the Call Stack, which in our case is simply one method call onGotFocus() as we can see in the image below.
When we click on onGotFocus() we will jump to the actual line of code in the Script tab. Let's place a breakpoint on that line by clicking on the gutter on the left margin, right to the left of the line number. Breakpoints are represented by a red circle on that margin.
Now let's click on the Name field again. Firebug kicks in an halts execution at the breakpoint we just set.
Looking at the Watch tab on the right, we can see that it is already tracking the value of this. And, to our surprise, this does not contain a reference to an instance of our BackgroundHighlighter. Instead it contains a reference to the input element.
Remember when we said you should be careful when using the this keyword in our objects? That was back in The perils of this. Well, that is precisely the problem we are having right now. Our onGotFocus() method is being called as an event handler for the onfocus event of the input field, and that call is made with the input field being the value of this.
Our problem is not on line 32 though. The problem is back a few lines before:
this.field['onfocus'] = this.onGotFocus; this.field['onblur'] = this.onLostFocus;
We cannot just pass a reference to one of our methods like that. We need to create some context that forces the this inside those methods to contain our object. This is not hard. Let's change those two lines to:
var that = this;
this.field['onfocus'] = function() {
that.onGotFocus();
};
this.field['onblur'] = function() {
that.onLostFocus();
};
Now save the page and refresh the browser. The page should work now.
The Console tab
The console tab is an interactive interface between you and the executing code. It contains a prompt denoted by >>> where you can inspect objects and variables, change their values, create new ones, etc.
Inspect DOM elements
The HTML tab allows us to inspect the page's structure. As we click elements in the displayed tree, they are highlighted in the page right above. It is very useful to understand where in the page hierarchy a given element is.
Inspect CSS
The CSS tab allows you to see all the CSS rules that exist on the page, from the various files that may contain CSS, and you can change or disable any individual attribute. The effects of changing the attributes are immediately reflected on the page. This feature is great for tweaking the CSS rules of your page to fix CSS bugs.
All your scripts
Using the Scripts tab you can see all the scripts in the page and set breakpoints to assist during debugging.
Network traffic
One of the most interesting tabs is the Net tab. It shows all the requests made during the load and operation of the page.
This tab is especially useful during the debugging of AJAX calls, where after expanding one of the requests we can see all the HTTP traffic information for that particular request. The HTTP headers can become very useful when tracing a problem.
Firebug has many more features but you're better off playing with it and learning which ones become more useful to you. This tool is under active development, so make sure you check their site often to get newer versions as they become available.
Error Handling and Debugging Conclusion
Dealing with software errors in one of those skills that can always be polished more. Beyond any natural aptitude, the key to be effective in error handling is understanding the language support and the existing development tools.
In case we didn't make it clear enough, we believe you should choose Firefox as your main development browser and install Firebug immediately.