Catch & Handle Runtime Errors in JavaScript

10 min read·Jan 1, 2026

In programming, exception handling is the process of dealing with unwanted or unexpected events (essentially errors) that might disrupt the normal execution flow of a computer program.

In Node.js, some of these errors like runtime errors can be caught or ignored, which means that the process can decide to either intercept them and perform an action in response, or just completely ignore them.

On the other hand, other errors like syntax errors cannot be caught or ignored, which means that the process will be forced to immediately terminate and the error must be corrected manually in order for the program to run as intended.

The try...catch statement

To prevent your application from unexpectedly crashing due to a runtime error, you can execute code that may throw an error within a try block.

If an error is thrown at runtime, the execution flow of the code will be altered and the control transferred automatically to the catch block. Once the statements in the catch block have been executed, the script will resume its normal, sequential execution.

try {
  // statements
} catch(error) {
  // handle or ignore
}

Where:

  • error is the value of the error thrown within the try block.

Note: The try block must always be used with a catch block, as otherwise the errors won't be caught and the process crash.

Example

Let's consider this script, that uses a try...catch block to gracefully catch and log any error thrown by the divide function:

function divide(a, b) {
  if (isNaN(a) || isNaN(b)) {
    throw Error('Operands must be numbers.');
  } else if (b === 0) {
    throw Error('Cannot divide by zero.');
  }
  return a / b;
}

let result;

try {
  result = divide('a', 2);
  console.log(result);
} catch(error) {
  console.error(error);
}

result = divide(4, 2);
console.log(result);

📚 In JavaScript, errors are programatically raised using the throw statement followed by Error and an optional error message.

throw Error('Error message');

When executed, it will:

  1. Call the divide() function with invalid arguments within the try block, which will throw an error causing the subsequent call to the console.log() function to be ignored.
  2. Catch and log the error thrown by the divide() function within the catch block.
  3. Call the divide() function with valid arguments outside of the try...catch block and output its return value.

Which will produce this output:

Error: Operands must be numbers.
    at divide (/Users/razvan/scripts/divide.js:3:11)
    at Object.<anonymous> (/Users/razvan/scripts/divide.js:13:12)
    at Module._compile (node:internal/modules/cjs/loader:1554:14)
    at Object..js (node:internal/modules/cjs/loader:1706:10)
    at Module.load (node:internal/modules/cjs/loader:1289:32)
    at Function._load (node:internal/modules/cjs/loader:1108:12)
    at TracingChannel.traceSync (node:diagnostics_channel:322:14)
    at wrapModuleLoad (node:internal/modules/cjs/loader:220:24)
    at Function.executeUserEntryPoint [as runMain] (node:internal/modules/run_main:170:5)
    at node:internal/main/run_main_module:36:49
2

As you can see here, the catch block caught the error thrown by the first call to the divide() function and prevented the script from terminating in an unexpected manner, allowing the execution of the second call to the divide() function.

The finally statement

The finally statement, used in conjunction with a try or try...catch block, allows you to execute a set of statements regardless of whether an error was raised or caught.

try {
  // execute potentially falty code
} catch(error) {
  // catch and process the error
} finally {
  // execute a final statement
}

Note: When using a try...finally block, the instructions in the finally block will be executed before the error raised in the try block interrupts the normal execution flow.

Example

Let's consider this script, that uses the finally block to execute an instruction before the script's execution is interrupted by an error:

function divide(a, b) {
  if (isNaN(a) || isNaN(b)) {
    throw Error('Operands must be numbers.');
  } else if (b === 0) {
    throw Error('Cannot divide by zero.');
  }
  return a / b;
}

try {
  console.log('(1) Executing the 1st `divide` function...');
  divide('a', 2);
} finally {
  console.log('(1) Done');
}

console.log('(2) Executing the 2nd `divide` function...');
divide(4, 2);
console.log('(2) Done');

When executed, it will:

  1. Execute the first call to the divide() function with invalid arguments within the try block, which will throw a TypeError.
  2. Execute the console.log in the finally block.
  3. Terminate the execution of the script without executing any other instructions declared outside of the try...finally block.

Which will produce this output:

(1) Executing the 1st `divide` function...
(1) Done
/Users/razvan/scripts/divide.js:3
    throw Error('Operands must be numbers.');
    ^

Error: Operands must be numbers.
    at divide (/Users/razvan/scripts/divide.js:3:11)
    at Object.<anonymous> (/Users/razvan/scripts/divide.js:12:3)
    at Module._compile (node:internal/modules/cjs/loader:1554:14)
    at Object..js (node:internal/modules/cjs/loader:1706:10)
    at Module.load (node:internal/modules/cjs/loader:1289:32)
    at Function._load (node:internal/modules/cjs/loader:1108:12)
    at TracingChannel.traceSync (node:diagnostics_channel:322:14)
    at wrapModuleLoad (node:internal/modules/cjs/loader:220:24)
    at Function.executeUserEntryPoint [as runMain] (node:internal/modules/run_main:170:5)
    at node:internal/main/run_main_module:36:49

Node.js v22.14.0

Enjoying the courses?

I've made these courses completely free so anyone can learn from them. If they've helped you and you'd like to actively support the work behind BackendBrewery, you can leave a tip:

Support BackendBrewery
Catch & Handle Runtime Errors in JavaScript | Backend Brewery