Skip to content
Laviyx Learn

Try

JavaScript course menu

JavaScript Error Handling: try, catch, finally, throw and Custom Errors

Learn error handling in JavaScript: built-in error types, try, catch and finally, throwing your own errors, custom error classes, the cause option, rethrowing, handling async errors, and global error handlers, with best practices.

Advanced10 min readUpdated
On this page

Things go wrong: a network request fails, a user types letters into a number field, a file is missing. Error handling lets your program respond sensibly instead of crashing or showing a blank page.

Built-in errors#

When JavaScript hits a problem it throws an error object. Common types:

ErrorTypical cause
ReferenceErrorUsing a variable that does not exist
TypeErrorUsing a value the wrong way: calling something that is not a function, reading a property of undefined
SyntaxErrorCode that cannot be parsed, or invalid JSON in JSON.parse
RangeErrorA number out of the allowed range, such as new Array(-1)

Every error has a name, a message, and a stack (the trace of function calls, very useful for debugging).

try...catch#

Code that might fail goes in try; if anything inside throws, execution jumps to catch:

JavaScript
const user = undefined;

try {
  console.log("Before the problem");
  console.log(user.name);
  console.log("This line never runs");
} catch (error) {
  console.log(`${error.name}: ${error.message}`);
}
console.log("The program keeps going");
Output
Before the problem
TypeError: Cannot read properties of undefined (reading 'name')
The program keeps going

If you do not need the error object, you can write just catch { ... }.

finally#

finally runs whether or not an error happened, even after a return. It is used for cleanup such as hiding a loading spinner or closing a file:

JavaScript
function parsePrice(text) {
  try {
    const value = JSON.parse(text);
    return `price is ${value.price}`;
  } catch {
    return "invalid data";
  } finally {
    console.log(`(finished parsing ${text})`);
  }
}

console.log(parsePrice('{"price": 499}'));
console.log(parsePrice("oops"));
Output
(finished parsing {"price": 499})
price is 499
(finished parsing oops)
invalid data

Throwing your own errors#

Use throw to signal that something is wrong. Always throw Error objects (not strings), so you get a stack trace:

JavaScript
function withdraw(balance, amount) {
  if (typeof amount !== "number" || Number.isNaN(amount)) {
    throw new TypeError("Amount must be a number");
  }
  if (amount > balance) {
    throw new RangeError(`Cannot withdraw ₹${amount}; balance is ₹${balance}`);
  }
  return balance - amount;
}

for (const amount of [200, 5000, "100"]) {
  try {
    console.log("New balance:", withdraw(1000, amount));
  } catch (error) {
    console.log(`${error.name}: ${error.message}`);
  }
}
Output
New balance: 800
RangeError: Cannot withdraw ₹5000; balance is ₹1000
TypeError: Amount must be a number

Custom error classes#

For your own kinds of failure, extend Error. Code can then react differently to each type with instanceof:

JavaScript
class ValidationError extends Error {
  constructor(field, message) {
    super(message);
    this.name = "ValidationError";
    this.field = field;
  }
}

function register({ email, age }) {
  if (!email.includes("@")) throw new ValidationError("email", "Email is not valid");
  if (age < 13) throw new ValidationError("age", "You must be at least 13");
  return "Registered!";
}

try {
  register({ email: "asha@example.com", age: 10 });
} catch (error) {
  if (error instanceof ValidationError) {
    console.log(`Problem with ${error.field}: ${error.message}`);
  } else {
    throw error;   // not ours: let it go up
  }
}
Output
Problem with age: You must be at least 13

Rethrowing errors you do not know how to handle is important: swallowing every error silently hides real bugs. Classes are covered in Classes.

Wrapping errors with cause#

When you catch a low-level error and throw a more meaningful one, keep the original as the cause:

JavaScript
function loadConfig(text) {
  try {
    return JSON.parse(text);
  } catch (error) {
    throw new Error("Could not load settings file", { cause: error });
  }
}

try {
  loadConfig("{bad json");
} catch (error) {
  console.log(error.message);
  console.log("Caused by:", error.cause.name);
}
Output
Could not load settings file
Caused by: SyntaxError

Async errors#

With async/await, rejected promises are thrown at the await, so try...catch works the same way (async and await). With .then() chains, use .catch().

try...catch does not catch errors thrown later inside a callback, such as a timer, because the try block has already finished by then. Handle errors inside the callback itself.

Global handlers#

As a last resort, you can log errors that nothing caught, for example to send them to an error-tracking service:

JavaScript
// Browsers
window.addEventListener("error", (event) => report(event.error));
window.addEventListener("unhandledrejection", (event) => report(event.reason));

// Node.js
process.on("uncaughtException", (error) => { report(error); process.exit(1); });

These are for reporting, not for recovering; fix the cause instead.

Best practices#

  • Catch errors where you can do something useful: show a message, retry, use a fallback.
  • Show users friendly messages and log technical details for developers.
  • Never leave an empty catch {} that hides failures, unless ignoring the error is truly intended (and commented).
  • Validate input early and throw clear, specific errors.

Practice#

Practice exercise

Write safeDivide(a, b) that throws a TypeError if either argument is not a number and a RangeError if b is zero, and otherwise returns a / b. Call it with (10, 2), (5, 0) and (4, "2") inside try...catch, printing either the result or the error name and message.

Show solution
JavaScript
function safeDivide(a, b) {
  if (typeof a !== "number" || typeof b !== "number") {
    throw new TypeError("Both arguments must be numbers");
  }
  if (b === 0) {
    throw new RangeError("Cannot divide by zero");
  }
  return a / b;
}

for (const [a, b] of [[10, 2], [5, 0], [4, "2"]]) {
  try {
    console.log(safeDivide(a, b));
  } catch (error) {
    console.log(`${error.name}: ${error.message}`);
  }
}
Output
5
RangeError: Cannot divide by zero
TypeError: Both arguments must be numbers

Key takeaway#

Finished this lesson?