In programming, as well as any other work or activity, no one is immune from mistakes. If error occurs, the program may crash and endanger the whole business project. In JS the try..catch
statement is specifically designed to prevent errors.
Syntax
try {
// the code
}
catch (err) {
// error handling
}
Here the code
is instructions that are checked for errors, and error handling
are commands that will be executed on exceptions (errors are also called exceptions).
The err
variable contains an exception object (the name of the error can be anything) that can be accessed in the catch block. Try and catch
is followed by statements in curly braces {}. They are required for one or more commands.
When an exception occurs, catch
will instantly take control and execute the statements in it. If no error occurs, then the catch block will not be executed. Inside catch there can be an Error object that has name and message attributes.
Thus, if an error occurs, execution is passed to catch and the script does not crash. Try constructs can be nested within each other. Let's highlight a few examples.
try {
alert('Start of code'); // (1) <--
// ...code without errors
alert('End of code'); // (2) <--
} catch(err) {
alert('An error has occurred!'); // (3)
}
Since there are no errors here, windows (1) and (2) will be displayed.
try {
alert('Start of code'); // (1) <--
lalala; // error, undefined variable!
alert('End of code'); // (2)
} catch(err) {
alert('An error has occurred!'); // (3) <--
}
Since there is an error here, windows (1) and (3) will be displayed. But there are exceptions here.
1. Try..catch
only works for errors that occur during code execution. Before execution, the code is read and its syntax is checked. For example, the number of curly braces may not match.
try {
{{{{{{{{{{{{
} catch(e) {
alert("This code is syntactically incorrect");
}
Such errors in JavaScript are called parsing errors. So try..catch
only understands errors that occur in valid code. These are run-time errors, also called exceptions.
2. Try..catch
works synchronously. An error in the code that will be executed in the future, for example, the setTimeout
will not be detected:
try {
setTimeout(function() {
noSuchVariable; // script will fall here
}, 1000);
} catch (e) {
alert( "fail" );
}
Since the function is inside try..catch
, an error cannot be caught. The following example shows the correct placement of try..catch
to detect the exception inside the scheduled function.
setTimeout(function() {
try {
noSuchVariable; // error occurs here
} catch {
alert( "error caught!" );
}
}, 1000);
Error object
The catch block contains the error identifier. All standard errors have the following attributes:
name
- containing the name of the errormessage
- containing the text of the message
Most browsers also use other non-standard properties, for example stack
- containing a string with the current call stack that resulted in the exception. This information is used to debug the program.
Example:
try {
lalala; // error, undefined variable!
} catch(err) {
alert(err.name); // ReferenceError
alert(err.message); // lalala is not defined
alert(err.stack); // ReferenceError: lalala is not defined at (...call stack)
// Just output the error as a string "name: message"
alert(err); // ReferenceError: lalala is not defined
}
Catch block without variable
Since this feature has only been recently added to the language, polyfill
is usually used in older browsers. If we don't want to use the error in the catch, we can skip it:
try {
// ...
} catch { // <-- without (err)
// ...
}
Let's take a look at more examples of using try..catch
.
Condition expressions
If catch contains conditional expressions, you can handle different kinds of exceptions.
try {
function(); // can throw different types of errors
} catch (error) {
if (error instanceof TypeError) {
// commands on error TypeError
} else if (error instanceof RangeError) {
// commands on error RangeError
} else if (error instanceof EvalError) {
// commands on error EvalError
} else {
// commands on other errors
logMyErrors(error); // handle the exception
}
}
Using JSON
Typically, the JSON format is used to transfer data over a network, from a server, or from another source. JavaScript can transform such data for reading using the JSON.parse
method. You can get them and call JSON.parse like this:
let json = '{"name":"John", "age": 30}'; // server data
let user = JSON.parse(json); // converted text view to JS-object
// now user can be used
alert( user.name ); // John
alert( user.age ); // 30
If the user enters incorrect data, the script crashes. To see what happened, you will have to open the console. How can you make the error visible for users? For this we use try..catch
:
let json = "{ incorrect JSON }";
try {
let user = JSON.parse(json); // <-- there is an error here...
alert( user.name ); // won't work
} catch (e) {
// ... execution is passed here
alert( " Sorry, there is an error in the data, please enter it again." );
alert( e.name );
alert( e.message );
}
As a result, we will receive an error message. But if we want to send a new network request, offer the visitor an alternative way, send information about the error to the server for logging - all this can be implemented in catch. This is much better than crashing.
Generating own errors
But what if JSON is written correctly, but lacks the required property? For instance:
let json = '{ "age": 35 }'; // data is incomplete
try {
let user = JSON.parse(json); // <-- will run without error
alert( user.name ); // no name property!
} catch (e) {
alert( "fail" );
}
Although JSON.parse
will execute without errors, we are not happy with the absence of a name property. In JavaScript, you can use the throw statement to throw an error.
throw <error object>
When the script reaches the error, it immediately stops executing and the control flow is passed to the next catch.
You can pass a primitive, a number, or a string as the error object, but an object is usually used. JavaScript already has a large number of standard errors built in. In addition, you can use them as constructors to create your own errors. Let's list the main ones:
Error - a basic constructor.
EvalError - displayed when using the global
eval()
function.InternalError - occurs when an internal error gets into the JavaScript engine (e.g. "too many switch cases").
RangeError - occurs when the value of a variable or parameter is not within a valid numeric range.
ReferenceError - occurs when you call a variable that does not exist.
SyntaxError - occurs when you interpret syntactically invalid code.
TypeError - occurs when a value passed to a function is not of the expected type.
URIError - occurs when invalid parameters are passed to
encodeURI()
ordecodeURI()
.
Let's create error objects:
let error = new Error(message);
// or
let error = new SyntaxError(message);
let error = new ReferenceError(message);
// ...
All built-in errors are objects in which the name property specifies the name of the constructor and the message property is passed as a value. For instance:
let error = new Error("Sorry, error! :-o");
alert(error.name); // Error
alert(error.message); // Sorry, error! :-o
Here's how it applies to JSON.parse:
try {
JSON.parse("{ bad json :-O }");
} catch(e) {
alert(e.name); // SyntaxError
alert(e.message); // Unexpected token b in JSON at position 2
}
JSON.parse
displays the SyntaxError. In addition, let's generate an error, since the absence of username (name) is also an error.
let json = '{ "age": 30 }'; // incomplete data
try {
let user = JSON.parse(json); // <-- will run without error
if (!user.name) {
throw new SyntaxError("You didn't enter a name"); // (*)
}
alert( user.name );
} catch(e) {
alert( "JSON Error: " + e.message ); // JSON Error: You didn't enter a name
}
Thanks to the throw
statement, an error (*) occurs, which corresponds to the standard in JavaScript. Catch takes over the control flow and displays a message. Now all errors are processed in the catch block: both for JSON.parse
and for other cases.
Throwing an exception
We can use try..catch
not just to handle invalid data, but also programmatic (undefined variable) and other errors.
Example:
let json = '{ "age": 30 }'; // incomplete data
try {
user = JSON.parse(json); // <-- error, no "let" before user
// ...
} catch(err) {
alert("JSON Error: " + err); // JSON Error: ReferenceError: user is not defined
// (incorrect error type)
}
In this example, all errors are handled in the same way, and for a definition error, the message "JSON Error:" is displayed. Let's separate the errors that we want to handle. This is accomplished using a technique called "throwing an exception".
Using the if ... else
instruction and the instanceof operator, we check the type of the error, and if it is the way we need it, we handle it, and if not, then forward it.
try {
myRoutine();
} catch(err) {
if (err instanceof RangeError) {
// handle a known exception with which
// understand what to do
} else {
throw err; // throw unknown errors
}
}
The algorithm for this method is:
1. The catch(err) block catches all errors.
2. In it, we parse the error object err.
3. If we don't know how to handle it, then we pass it using throw err.
Now we use throwing exceptions, where only SyntaxErrors are handled:
let json = '{ "age": 30 }'; // incomplete data
try {
let user = JSON.parse(json);
if (!user.name) {
throw new SyntaxError("You didn't enter a name");
}
blabla(); // unexpected error
alert( user.name );
} catch(e) {
if (e.name == "SyntaxError") {
alert( "JSON Error: " + e.message );
} else {
throw e; // rethrow (*)
}
}
The throw *
statement from the catch block throws out the error e
, as a result, the script crashes or it can be detected by another external try..catch
construct (if any).
Let's see how to catch the error using the external try..catch
construct:
function readData() {
let json = '{ "age": 30 }';
try {
// ...
blabla(); // error!
} catch (e) {
// ...
if (e.name != 'SyntaxError') {
throw e; // throwing exception (unknown error)
}
}
}
try {
readData();
} catch (e) {
alert( " Outer catch caught: " + e ); // caught!
}
The readData
function can only handle SyntaxError, while the outer try..catch
understands all errors.
The Finally block
The try..catch
construction can contain one more block: finally
. When this block is in try
, it is executed anyway:
if no error occurred, then after
try
if an error occurred, then after
catch
This is what the try construct looks like with this block:
try {
... try to execute the code...
} catch(e) {
... handling errors...
} finally {
... we always do...
}
Example:
try {
alert( 'try' );
if (confirm('Make an error?')) BAD_CODE();
} catch (e) {
alert( 'catch' );
} finally {
alert( 'finally' );
}
There are two ways of doing this:
If the answer to the question ("Make a mistake?") is positive, windows with
catch
andfinally
are displayed.If the answer is negative,
finally
is displayed.
The finally
block is often used when we have started something and want to complete it anyway.
For example, to measure the time it takes for the Fibonacci function fib (n). But what if there was an error in the function? For example, fib (n)
in the code below throws an error for negative and non-integers.
Here you can successfully use finally
to take measurements no matter what - in case of success of fib
and in case of error:
let num = +prompt("Enter a positive integer:", 35)
let diff, result;
function fib(n) {
if (n < 0 || Math.trunc(n) != n) {
throw new Error("This number is not a non-negative integer");
}
return n <= 1 ? n : fib(n - 1) + fib(n - 2);
}
let start = Date.now();
try {
result = fib(num);
} catch (e) {
result = 0;
} finally {
diff = Date.now() - start;
}
alert(result || "error!");
alert( " Execution took ${diff}ms" );
Let's put 35 into the prompt - the script will complete normally, finally will execute after the try. Now enter -1 - an error will be thrown immediately, and the time will be 0ms. In any case, the measurements are correct.
But there are a few things to pay attention to.
Variables inside
try..catch..finally
are local.The result and diff variables in the code are declared before
try..catch
.If a variable is declared inside a block, it will not be available after it.
The finally
block is executed both on error and if there is a return in try..catch
. Below, try returns return, but control is passed to finally
before control is returned to the outer code.
function func() {
try {
return 1;
} catch (e) {
/* ... */
} finally {
alert( 'finally' );
}
}
alert( func() ); // first the alert from finally is executed and then this one
When using the try..finally
construct without catch, we do not handle errors (they will be dropped), but we remain sure that the started processes have terminated.
function func() {
// measurements that need to be completed
try {
// ...
} finally {
// complete them even if everything falls down
}
}
Here, the error is always displayed, but finally
starts before the flow of control leaves the function.
Let's look at another example used when the script does not work correctly, e.g. to shut down safely, you might need to free memory and resources.
let connection = {
open: function () {
console.log('open a connection');
},
close: function () {
console.log('close the connection');
}
};
try {
connection.open();
} catch (err) {
console.log(err.message);
} finally {
connection.close();
}
Here we are closing the connection by calling the close method of the connection object in finally, regardless of whether an exception has occurred or not.
Summary
The try..catch
construct allows you to track errors during script execution. With its help, you can run specific code that responds to errors.
Syntax:
try {
// executing the code
} catch(error) {
// jump here on error
// error – exception object
} finally {
// always executed after try/catch
}
The try..catch
construct must contain at least two blocks, that is, the following options are possible:
try..catch
try..finally
try..catch..finally
Error objects with the following properties are passed to the catch block:
name
- the name of the error (or error constructor)message
- text message about the errorstack
(supported by most browsers) - the stack at the time of the error
If the exception object is not used, it can be skipped using:
// We can omit error handling
try {
// some code...
} catch {
// ignore error object
}
// Instead of classic form
try {
// some code...
} catch (err) {
// handle an error
}
We can also generate errors ourselves using the throw
statement. The throw argument can be anything, but most often it is an error object that inherits from the built-in Error class.
Throwing an exception is often used - this is an error handling technique using throw
. We condition the catch block to handle only a certain type of error, so it will rethrow all other errors.