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..catchworks 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 error

  • message - 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:

  1. Error - a basic constructor.

  2. EvalError - displayed when using the global eval() function.

  3. InternalError - occurs when an internal error gets into the JavaScript engine (e.g. "too many switch cases").

  4. RangeError - occurs when the value of a variable or parameter is not within a valid numeric range.

  5. ReferenceError - occurs when you call a variable that does not exist.

  6. SyntaxError - occurs when you interpret syntactically invalid code.

  7. TypeError - occurs when a value passed to a function is not of the expected type.

  8. URIError - occurs when invalid parameters are passed to encodeURI() or decodeURI().

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:

  1. If the answer to the question ("Make a mistake?") is positive, windows with catch and finally are displayed.

  2. 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 error

  • stack (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.