Sentry Answers>JavaScript>

JavaScript Error Handling: Throwing and Catching Exceptions

JavaScript Error Handling: Throwing and Catching Exceptions

David Y.

The Problem

I’m new to JavaScript development and would like to learn more about error handling.

I’ve seen trycatch statements in code, and I understand that they can be used to suppress error messages, but I don’t understand why one would do this rather than fixing the code so that the error doesn’t come up.

How does error handling work and when should I use it?

The Solution

While it may be possible to fix most or all possible error conditions in very simple programs, this becomes increasingly difficult as code size and complexity increase.

Programs that rely on external inputs, such as user input or data from an API, can encounter error states due to failures in those external sources.

For example, external inputs could be flawed due to:

  • A user providing incorrectly formatted data.
  • An API becoming unavailable.

Error handling with trycatch allows developers to build execution flows that handle these unavoidable errors gracefully, without crashing the program.

How To Use Error Handling

Let’s consider a simple example. The below code asks the user to enter a number for it to double:

Click to Copy
function doubleInput() { const userInput = prompt("Please enter a number:"); // prompt the user for a number try { const number = parseInt(userInput); // attempt to parse user input as an integer if (isNaN(number)) { // unparseable input will be stored as NaN (not a number) throw new Error("Invalid number entered"); // throw an error – this will move execution to the catch block below } console.log("Double that number is:", number * 2); // show number*2 in the console } catch (error) { console.error("Error:", error.message); // print the thrown error's message in the console alert("Please enter a valid number!"); // show an error message to the user doubleInput(); // ask the user again } } doubleInput(); // ask the user for a number and double it

Running this code will produce a prompt asking for a number.

In the try block, the code attempts to parse that input as an integer.

  • If it succeeds, it doubles the number and prints the result to the console.
  • If it fails, it logs an internal error message, displays another, more user-oriented error message, and starts again.

The code will continue to prompt the user for a valid number until one is entered.

Code in a try block will run until a throw statement is encountered. At this point, the remainder of the try block will be abandoned, and execution will switch to the nearest catch block. This will happen no matter how deeply nested the throw statement is. The code below demonstrates this:

Click to Copy
function printA() { const userInput = prompt("Please enter a number:"); // prompt the user for a number try { const number = parseInt(userInput); // attempt to parse user input as an integer if (isNaN(number)) { // unparseable input will be stored as NaN (not a number) throw new Error("Invalid number entered"); // throw an error – this will move execution to the catch block below } console.log("A".repeat(10 / number)); // print A (10/number) times } catch (error) { console.error("Error:", error.message); // print the thrown error's message in the console alert("Please enter a valid number!"); // show an error message to the user printA(); // ask the user again } } printA(); // ask the user for a number and use it to print one or more As

This code can fail in two ways:

  • If a user provides an input that cannot be parsed as an integer, such as a, it will output the error message Invalid number entered and prompt the user to enter a valid number.
  • If a user provides the number 0, it will output the error message Invalid count value: Infinity and prompt the user to enter a valid number.

In the first case, the code behaves as explained in the previous example. In the second case, the error arises from providing the value Infinity to String.prototype.repeat(), causing it to throw a RangeError, which is a subclass of Error.

The instanceof operator can be added to the catch block to determine which error has been thrown:

Click to Copy
function printA() { const userInput = prompt("Please enter a number:"); // prompt the user for a number try { const number = parseInt(userInput); // attempt to parse user input as an integer if (isNaN(number)) { // unparseable input will be stored as NaN (not a number) throw new Error("Invalid number entered"); // throw an error – this will move execution to the catch block below } console.log("A".repeat(10 / number)); // print A (10/number) times } catch (error) { console.error("Error:", error.message); // print the thrown error's message in the console if (error instanceof RangeError) { alert("Zero is not allowed."); // show a specific error message to the user } else { alert("Please enter a valid number!"); // show a general error message to the user } printA(); // ask the user again } } printA(); // ask the user for a number and use it to print one or more As

Sometimes, trycatch is accompanied by a third block: finally. This block specifies the code to run regardless of whether an error is thrown.

For example, the finally block could be used to make the code prompt the user for another number regardless of whether it has succeeded or failed.

Click to Copy
function printA() { const userInput = prompt("Please enter a number (or X to exit):"); // prompt the user for a number let timeout = 1000; if (userInput.toUpperCase() === "X") { // exit the function return; } try { const number = parseInt(userInput); // attempt to parse user input as an integer if (isNaN(number)) { // unparseable input will be stored as NaN (not a number) throw new Error("Invalid number entered"); // throw an error – this will move execution to the catch block below } console.log("A".repeat(10 / number)); // print A (10/number) times timeout = 2000; // wait 2 seconds before asking again } catch (error) { console.error("Error:", error.message); // print the thrown error's message in the console if (error instanceof RangeError) { alert("Zero is not allowed."); // show a specific error message to the user } else { alert("Please enter a valid number!"); // show a general error message to the user } timeout = 1000; // wait one second before asking again } finally { setTimeout(function () { printA(); // ask the user again after a delay }, timeout); } } printA(); // ask the user for a number and use it to print one or more As

In this example, the catch block could even be omitted entirely, creating a tryfinally flow:

Click to Copy
function printA() { const userInput = prompt("Please enter a number (or X to exit):"); // prompt the user for a number let timeout = 1000; if (userInput.toUpperCase() === "X") { // exit the function return; } try { const number = parseInt(userInput); // attempt to parse user input as an integer if (isNaN(number)) { // unparseable input will be stored as NaN (not a number) throw new Error("Invalid number entered"); // throw an error – this will move execution to the finally block below } console.log("A".repeat(10 / number)); // print A (10/number) times timeout = 2000; // wait 2 seconds before asking again } finally { setTimeout(function () { printA(); // ask the user again after a delay }, timeout); } } printA(); // ask the user for a number and use it to print one or more As

As the code in the finally block will execute regardless of whether an error is thrown in the try block, the code produces substantially the same behavior as our previous examples, only without the user-friendly error messages.

Without tryfinally, our the would crash as soon as an error was thrown, so this can be a useful way to recover from the failure of non-essential code paths.

When To Use Error Handling

Now that we know how error handling works, we can think more productively about when to use it.

Using trycatchfinally allows us to significantly alter the execution flow of our code and to recover gracefully from what would otherwise be fatal errors. It can be tempting to pepper our code with trycatch blocks in the hopes of making it more robust, but this would not only make our code more complex, it could also make the debugging process more difficult.

Consider the following code:

Click to Copy
function printA() { const userInput = prompt("Please enter a number (or X to exit):"); // prompt the user for a number let timeout = 1000; if (userInput.toUpperCase() === "X") { // exit the function return; } try { const number = parseInt(userInput); // attempt to parse user input as an integer if (isNaN(number)) { // unparseable input will be stored as NaN (not a number) throw new Error("Invalid number entered"); // throw an error – this will move execution to the catch block below } console.log("A".repeat(10 / number)); // print A (10/number) times timeout = 2000; // wait 2 seconds before asking again } catch (error) { alert("An error occurred, please try again.") timeout = 1000; // wait one second before asking again } finally { setTimeout(function () { printA(); // ask the user again after a delay }, timeout); } }

We know from the previous examples that this code has two possible failure states:

  • The Error thrown explicitly in the try block.
  • The RangeError thrown by repeat() when a user provides the value Infinity.

However, if we didn’t know about the RangeError failure state, the code above would make it almost impossible to discover because it doesn’t log the caught error.

In this way, overly simple catch blocks can mask issues in code that would otherwise lead to clear errors, potentially causing more subtle bugs later on.

For this reason, it is best practice to:

  • Use error handling deliberately and as a last resort, when errors are unavoidable in ordinary program flow.
  • Always log caught errors to ensure that developers can fully understand individual failure cases.
  • YoutubeHow Sentry.io saved me from disaster
  • ResourcesImprove Web Browser Performance - Find the JavaScript code causing slowdowns
  • SentryJavascript Error Monitoring & Tracing
  • ResourcesJavaScript Frontend Error Monitoring 101
  • Syntax.fmListen to the Syntax Podcast
  • Syntax.fm logo
    Listen to the Syntax Podcast

    Tasty treats for web developers brought to you by Sentry. Get tips and tricks from Wes Bos and Scott Tolinski.

    SEE EPISODES

Considered “not bad” by 4 million developers and more than 100,000 organizations worldwide, Sentry provides code-level observability to many of the world’s best-known companies like Disney, Peloton, Cloudflare, Eventbrite, Slack, Supercell, and Rockstar Games. Each month we process billions of exceptions from the most popular products on the internet.

© 2025 • Sentry is a registered Trademark of Functional Software, Inc.