Error handling
Error objects
Normally when an error occurs, the JavaScript engine creates a new error object, throws it and stops further execution of the program.
The error can be viewed in the browser's console. Besides a generic Error
type, there are other
core error types
in JavaScript, like TypeError
, ReferenceError
or SyntaxError
.
The error type thrown depends on the error that occurs.
A SyntaxError
will normally be thrown during parsing/compiling of the code if the code contains invalid syntax.
After parsing/compiling, with valid syntax, the program will be run by the computer's processor.
Errors that occur during that phase are called runtime errors or exceptions.
Runtime errors can be "caught" and handled by the code itself, as we will discover in this chapter.
It is also possible to include statements in the code that throw programmer-defined exceptions.
throw
A statement with keyword throw
, followed by an expression, throws an exception.
The expression will be thrown. The execution of the rest of the program will terminate (unless there is a catch
statement, which will be explained later this chapter).
The thrown exception (the statement's expression) may be any object or primitive, but generally it is better to use an instance of one of the exception types.
The generic Error
object, as well as
all its possible exception types,
have their own
constructor, so an exception that is an Error
object can be created with the
keyword new
.
function check(n) {
if (!(n >= -500 && n <= 500)) {
throw new RangeError("The argument must be between -500 and 500.");
}
};
check(1000); // throws: RangeError: The argument must be between -500 and 500.
An error object has two main properties: name
, which is the error type (RangeError
in the example above)
and message
, which is a string describing the error. These properties can be used in the catch
block,
as we will discover later this chapter.
try...catch
As mentioned before, we can "catch" an exception and handle it in some desired way.
We can accomplish this by using a try...catch
statement.
A try...catch
statement consists of a try
block,
followed by either a catch
block, a finally
block or both.
The try
block is executed first. If execution of the try
block throws an exception,
execution of this block stops and execution is transfered to the catch
block (if it exists) and
the exception will be passed to the catch
block. So, if the try
does not throw an error, the catch
block will be ignored.
The code in the finally
block will always execute, regardless of whether the try
block threw an exception or not.
A finally
block is to finalize or "cleanup" the process that started in the try
block.
try {
nonExistentFunction();
} catch (error) {
console.error(error);
};
console.log("Execution continues...");
// Logs:
// ReferenceError: nonExistentFunction is not defined
// "Execution continues..."
In the above example the thrown exception was caught.
Execution of the try
block stopped when the error occurred, but execution of the overall program did not stop!
Without a catch
block, the program would have stopped!
Only a finally
block would still have been executed.
Without a catch
block, the finally
block is mandatory:
only a try
statement, without a catch
or finally
block, is not allowed.
try {
nonExistentFunction();
} finally {
console.log("Executed anyhow");
};
console.log("Execution continues...");
// logs:
// "Executed anyhow"
// Uncaught ReferenceError: nonExistentFunction is not defined
A try...finally
statement like above may be useful when, for instance, the try...finally
statement is nested within the try
block of an
other try...catch
statement and all the possible errors should be caught by the outer catch
block.
try {
nonDeclaredVar = 3;
try {
nonExistentFunction();
} finally { };
} catch (err) {
console.error(`Oops, a ${err.name} occurred. The error is: ${err.message} `);
};
// In strict mode it logs:
// "Oops, a ReferenceError occurred. The error is: assignment to undeclared variable nonDeclaredVar"
// In non-strict mode it logs:
// "Oops, a ReferenceError occurred. The error is: nonExistentFunction is not defined "
BTW: In non-strict mode nonDeclaredVar
declares as an
implicit global.
BTW: As previously mentioned: an error object has two main properties: name
and message
. They are both used here in the
catch
block.
Rethrowing
Typically a try...catch
statement is used when the programmer assumes a reasonable possibility that a specific
error will occur while executing a certain portion of code. In the catch
block that specific error will be handled.
But it is also possible that other, unexpected errors occur during the execution of the try
block.
They will also be "handled" by the catch
block, but the code in this block was not intended for other errors than the specific
expected error. In addition, the program will continue to run, even if a potentially malicious error has occurred.
function check(n) {
if (!(n >= -500 && n <= 500)) {
throw new RangeError("The argument must be between -500 and 500.");
}
};
try {
nonExistentFunction();
check(1000);
} catch (error) {
console.error(error);
// handle a possible RangeError occurring in check()...
};
console.log("Execution continues...");
// logs:
// ReferenceError: nonExistentFunction is not defined
// "Execution continues..."
In the example above the execution of the program continues with the false assumption that function nonExistentFunction()
executed.
The ReferenceError
was caught, but likely not handled the proper way.
The catch
block was designed to handle a RangeError
in the check
function, but this function did not even get to be called.
This all went south because of an unexpected error: the calling of a non-existent function. This can be solved by filtering the error, handling the intended error and
rethrowing any other error.
function check(n) {
if (!(n >= -500 && n <= 500)) {
throw new RangeError("The argument must be between -500 and 500.");
}
};
try {
nonExistentFunction();
check(1000);
} catch (error) {
if (error instanceof RangeError) {
console.error(error);
// handle a possible RangeError occurring in check()...
} else {
throw error
}
};
console.log("Execution continues...");
// logs: Uncaught ReferenceError: nonExistentFunction is not defined
A more elaborate example:
function check(n) {
if (n <= -500 || n >= 500) {
throw new RangeError(`${n} is not between -500 and 500`);
}
return n;
};
let succeeded = false;
while (!succeeded) {
try {
let num = Math.floor(Math.random() * 2000 - 1000); // random number between -1000 and 1000
console.log(check(num));
succeeded = true;
} catch (e) {
if (e instanceof RangeError) {
console.log(`${e.message}. Trying a new number...`);
} else {
throw e;
}
}
}
// One possible log:
// "-793 is not between -500 and 500. Trying a new number..."
// "600 is not between -500 and 500. Trying a new number..."
// -33
Asynchronous errors
Cases where programmers may expect possible errors to occur often involve loading of external resources, such as a file that contains data that the program needs.
The loading itself may be unsuccessful or the data may contain invalid syntax. Errors like these cannot be caught and handled with a regular try...catch
statement.
The loading takes time, and in the meantime the execution of the program, including the try...catch
statement, continues.
By the time a possible error occurs, the program already finished the try...catch
statement (and likely skipped the catch
block).
Functions like this, functions that take their time while the rest of the program execution continues, are called
asynchronous functions. JavaScript has its own way to handle them, with constructs that are
somewhat similar to the try...catch
statement. Later in this tutorial
asynchronous JavaScript will be explained in much more detail.
For now a simple example of a
setTimeout
function, which is asynchronous, used in a try...catch
statement.
try {
setTimeout(function() {
nonExistentFunction();
}, 1000);
} catch (e) {
console.error(`Whoops, a ${e.name} occurred. The error is: ${e.message} `);
};
// logs after one second:
// Uncaught ReferenceError: nonExistentFunction is not defined
The catch
block was skipped. Note that placing the try...catch
statement in the setTimeout
function
is a way to solve the problem.