This tutorial is out of date and no longer maintained.
Every programmer out there wants to write code that works. In the long run, chances of your program running as expected reduce due to one thing ‒ errors.
Errors in JavaScript could get complex at certain times and when that happens, programmers have no one to blame but themselves or the language itself. It would be great if we all knew how to detect these errors, expose them and prevent them from happening again.
My objective is to show you how JavaScript errors can be handled properly and avoided in your code. When you are done, you won’t be scared of frequently implementing features such as the stack
property as well as handling errors.
Usually, JavaScript errors can be divided into two categories, actual problems, and programmer mistakes. Actual problems are events that are not preventable by the programmer. For example, a program asking a user to enter a name, and it gets back an empty string.
Programmer mistakes are errors made by the programmer when writing programs. They can be further subdivided into:
These are the most basic kind of errors. Also known as parsing errors, they occur at compilation time in traditional programming languages and interpretation time in JavaScript. Check out the code block below:
var x = 2;
var y = 3;
console.log(x + y;
In the example above, the last line of code will cause a syntax error because it is missing a closing parenthesis. Usually, when a syntax error occurs in JavaScript, the rest of the code in other threads will get executed if they contain nothing that depends on the code containing the error.
Also known as exceptions, these kind of errors occur when your program is executed, usually when it has been compiled or interpreted. Consider the code block below:
var windowObject;
var windowFeatures = "menubar=yes,location=yes,resizable=yes,scrollbars=yes,status=yes";
function openPopup() {
windowObject = window.openObject("http://www.bbc.com/", "BBC_WindowName", strWindowFeatures);
}
The function above will cause a runtime error because although the syntax is correct, at runtime it is trying to call a method openObject()
that doesn’t exist.
The most complex of all three errors, logical errors occur when you make a mistake or flaw in the logic that controls your program’s script. This mistake causes the program to alter expected results or exhibit unexpected behavior. It’s usually very tricky trying to catch and even correct logical errors, consider the example below:
function power(base, exponent) {
var result = 1;
for (var count = 0; count < exponent; count++)
result *= base;
return result;
}
What if someone tries to call power ("JavaScript", 10)
? Well, that’s obviously a programmer’s mistake. What about power (5, 0.2)
? A quick look at the function’s logic will tell you that it can’t handle fractional exponents but by the law of Mathematics, raising a number to the halfth power is possible. JavaScript does this via the Math.pow()
function. In such situations where it’s not entirely clear what kind of input a function accepts, explicitly stating the kind of arguments that are acceptable in a comment or input validation becomes a good idea.
One common criticism of JavaScript is the way errors are handled through callbacks. Check out the pattern in which the code below is written:
var myFunc = function(cb) {
doSomething(function (err, a) {
if (err) return cb(err)
doSomethingElse(function (err, b) {
if (err) return cb(err)
return cb(null, [a, b])
})
})
}
What’s good about this kind of pattern? It forces programmers to handle errors. As the person writing the code, you always want to make sure you know when an operation can fail, especially if it’s an asynchronous operation.
What’s bad about this kind of pattern? It seems like a lazy way of writing code. Asides from that, if part of the code fails, the entire operation will fail. Certain exceptions to this retry logic, reverting changes, and advanced error reporting.
Fortunately, there are quite a number of ways and methods to deal with errors and exceptions. Let’s check out these methods and how they can be applied to our code:
The try…catch…finally
statement marks a block of statements to run in your code and specifies a response should an exception be thrown. It s possible to catch
logical and runtime errors but not syntax errors. Below is the syntax for the try…catch…finally
statement:
try {
// Code to run
[
break;
]
} catch (e) {
// Code to run if an exception occurs
[
break;
]
}
[
finally {
// Code that is always executed regardless of
// an exception occurring
}
]
try
statements are the statements to be executed. If an exception occurs during the execution of the try
statement, the exception is placed in e
and the catch
clause is executed otherwise, the catch
clause is skipped. The finally
clause executes after the try
statement is finished, it executes regardless of whether or not an exception was thrown or caught. try
statements can be used either with just the catch
clause, just the finally clause
or both. Check out this demo using just the try
statement and the catch
clause:
https://codepen.io/codebeast/pen/oozWbw
Using the finally
clause allows you to execute an additional command after try…catch
. Here’s an example:
https://codepen.io/codebeast/pen/qVamZm
The throw
statement is used to generate user-defined exceptions. During runtime, when a throw
statement is encountered, execution of the current function will stop and control will be passed to the first catch
clause in the call stack. If there is no catch
clause, the program will terminate. Check out this example showing how to use a throw
statement:
https://codepen.io/codebeast/pen/pdEPgM
The onerror()
method was the first event handler to facilitate and handle errors in JavaScript. It is often used with the syntax window.onerror
. This enables the error event to be fired on the window object whenever an error occurs during runtime. Below is an example showing how to use the onerror()
method:
https://codepen.io/codebeast/pen/RjGVaM
Another utility mode for onerror()
is using it to display an error message in case there is any error when loading images in your site:
<img src="coolPhoto.jpg" onerror="alert('An error occurred loading yor photo.')" />
The stack
property is a feature in the JavaScript Error object. It offers a trace of which functions were called, in what order, from which line and file, and with what arguments, proceeding from the most recent calls to earlier ones all the way to the original global scope call. Check out this code block demonstrating the stack
property:
function trace() {
try {
throw new Error('myError');
} catch (e) {
alert(e.stack);
}
}
function b() {
trace();
}
function a() {
b(3, 4, '\n\n', undefined, {});
}
a('first call, firstarg');
Assuming the above markup is saved as C:\stackoverflow.js
on a Windows file system it produces an alert message box with the following text:
- trace@file:///C:/stackoverflow.js:4:17
- b@file:///C:/stackoverflow.js:11:13
- a@file:///C:/stackoverflow.js:14:13
- @file:///C:/stackoverflow.js:15:9
A warning though, stack
is a nonstandard feature. It shouldn’t be used on production sites facing the web as it will not work for every user.
Usually, errors in asynchronous code require a large amount of if… else
checks and a careful inspection of parameter values. Promises allow asynchronous code to apply structured error handling. When using promises, you can process errors by passing an error handler to the then
method or using a catch
clause. Just like exceptions in regular code, an exception or rejection in asynchronous code will jump to the nearest error handler. Check out the code block below:
var log = "";
function doWork() {
log += "W";
return Promise.resolve();
}
function doError() {
log += "E";
throw new Error("oops!");
}
function errorHandler(error) {
log += "H";
}
These functions were created to show you how to process errors using the then
method. We’ll use them with the following code:
doWork()
.then(doWork)
.then(doError)
.then(doWork) // this will be skipped
.then(doWork, errorHandler)
.then(verify);
function verify() {
expect(log)
.toBe("This");
done();
}
What’s expected is that the log variable will contain “WWEH” when the code finishes executing, meaning the flow of calls with reach doWork
, then doWork
, then doError
, then errorHandler
. There are two things we can observe from this. The first is that when the call to doError
throws an exception, execution jumps to the next rejection handler which is errorHandler
, and skips over any potential success handlers. This behavior is obvious once you think of promises as a tool to transform asynchronous code into a procedural flow of method calls. In synchronous code, an exception will jump over statements and up the stack to find a catch handler, and the asynchronous code in this example is no different.
The second observation is that the verify
function will execute as a success handler after the error. Just like normal execution can resume in procedural code after a catch
clause, normal execution can resume with promises after a handled error. The verify
function executes because the error handler returns a successfully resolved promise. Remember that it’s the then
method’s job to return a new promise, and unless the error handler explicitly rejects a new promise, the new promise resolves successfully.
A promise object also provides a catch
clause to handle errors. Check out this example which is written using a catch
clause:
doWork()
.then(doWork)
.then(doError)
.then(doWork)
.then(doWork)
.catch(errorHandler)
.then(verify);
The catch
clause takes only a rejection handler method. There can be a difference in behavior between the following two code snippets:
.then(doWork, errorHandler)
… and …
.then(doWork)
.catch(errorHandler)
In the first snippet, if the success handler throws an exception or rejects a promise, execution will not go into the error handler since the promise was already resolved at this level. With catch
, you can always see an error that was not handled by the previous success handler.
Finally, imagine you have a rejected promise in your code, but there is no error handler attached. You can simulate this scenario with the following line of code:
Promise.reject("error!");
Some native environments and promise polyfills will warn you about unhandled promise rejections by displaying a message in the console of the developer tools. An unhandled promise rejection could spell doom your application as it might be leaving a critical error unattended, too.
When it comes to handling errors as a programmer, you either choose to ignore them and play the pretend game or become a superhero and go back in time to save the world - or in this case, your code. Personally, I would recommend becoming a superhero, ignoring errors will only lead to more and more errors until the entire stack becomes impossible to work on or even to attempt a bug fix. There’s no harm in trying, no shame in admitting failure. There will always be mistakes, it’s what we do about them that matters in the end.
Thanks for learning with the DigitalOcean Community. Check out our offerings for compute, storage, networking, and managed databases.
This textbox defaults to using Markdown to format your answer.
You can type !ref in this text area to quickly search our full set of tutorials, documentation & marketplace offerings and insert the link!
Sign up for Infrastructure as a Newsletter.
Working on improving health and education, reducing inequality, and spurring economic growth? We'd like to help.
Get paid to write technical tutorials and select a tech-focused charity to receive a matching donation.