SKU // 109

JavaScript Error Statements

JavaScript has four statements for working with errors. try marks code that might fail, catch handles the failure, finally runs cleanup no matter what, and throw lets you raise your own errors. Together they let a program recover from problems instead of stopping.

// try and catch

If nothing goes wrong, the catch block is skipped. If an error is thrown, the rest of the try block is skipped and catch runs:

function test(value) {
  try {
    console.log("start");
    console.log(value.toUpperCase());
    console.log("end of try");
  } catch (error) {
    console.log("caught: " + error.message);
  }
}

test("hi");
test(null);
start HI end of try start caught: Cannot read properties of null (reading 'toUpperCase')

The second time, "end of try" never prints: execution jumped straight from the failing line into catch.

Tip
If you do not need the error object, you can leave out the parameter: catch { ... } (added in 2019).

// finally

The optional finally block runs after try and catch, whether an error happened or not. Use it for cleanup, like hiding a "Loading..." message or resetting a form:

function load(text) {
  try {
    const data = JSON.parse(text);
    console.log("loaded", data.name);
  } catch (error) {
    console.log("bad data");
  } finally {
    console.log("hide the spinner");
  }
}

load('{"name": "Ava"}');
load("oops");
loaded Ava hide the spinner bad data hide the spinner

finally even runs when try returns early:

function check() {
  try {
    return "from try";
  } finally {
    console.log("finally still runs");
  }
}

console.log(check());
finally still runs from try
Watch out: return inside finally
A return in finally replaces whatever try or catch returned, and even swallows an error that was about to be thrown. function f() { try { return 1; } finally { return 2; } } returns 2. Keep finally for cleanup only.

// throw

throw raises your own error. Execution stops immediately and jumps to the nearest catch (or stops the script if there is none):

function setAge(age) {
  if (age < 0 || age > 120) {
    throw new RangeError("Age must be between 0 and 120");
  }
  return age;
}

try {
  setAge(200);
  console.log("never printed");
} catch (error) {
  console.log(error.name + ": " + error.message);
}
RangeError: Age must be between 0 and 120

// Throw Error Objects, Not Strings

JavaScript lets you throw anything: a string, a number, a boolean. W3Schools shows throw "Too big"; and throw 500;. It works, but avoid it:

try {
  throw "Too big";
} catch (error) {
  console.log(typeof error, error.message, error.stack);
}

try {
  throw new Error("Too big");
} catch (error) {
  console.log(typeof error, error.message, typeof error.stack);
}
string undefined undefined object Too big string
Not recommended: throwing strings or numbers
A thrown string has no name, no message, and no stack (the record of where it happened), so code that handles errors the normal way breaks, and the console cannot show you where it came from. Always throw new Error("...") or one of its types like new TypeError("...").

// Real Example: Input Validation

W3Schools' main example checks a number typed into a box. Here it is as a plain function, so you can run it without a web page:

function checkInput(text) {
  try {
    if (text.trim() === "") throw new Error("empty");
    const x = Number(text);
    if (Number.isNaN(x)) throw new Error("not a number");
    if (x < 5) throw new RangeError("too low");
    if (x > 10) throw new RangeError("too high");
    return "Input is OK: " + x;
  } catch (error) {
    return "Input is " + error.message;
  }
}

for (const text of ["", "abc", "3", "12", "7"]) {
  console.log(checkInput(text));
}
Input is empty Input is not a number Input is too low Input is too high Input is OK: 7

On a real page, W3Schools reads the box with document.getElementById("demo").value (find the element whose id is "demo" and read what was typed) and shows the result with element.innerHTML = ... (replace the element's content). Its finally version adds finally { document.getElementById("demo").value = ""; } to clear the box after every check, whether it passed or failed.

Note
W3Schools' version tests isNaN(x) on the text. The global isNaN converts first (JS NaN), so it happens to work here, but Number.isNaN(Number(x)) says exactly what you mean. W3Schools also mentions HTML's built-in checks, like <input type="number" min="5" max="10">. The browser enforces those before your code runs, but you should still check in JavaScript, because that HTML can be removed or bypassed.

// Only Catch What You Expect

A catch block catches every error, including bugs you did not plan for. Check the type, handle the ones you understand, and rethrow the rest:

function parseSettings(text) {
  try {
    return JSON.parse(text);
  } catch (error) {
    if (error instanceof SyntaxError) {
      console.log("Bad settings, using defaults");
      return {};
    }
    throw error;               // not ours to handle: pass it on
  }
}

console.log(parseSettings('{"theme": "dark"}'));
console.log(parseSettings("theme=dark"));
{theme: "dark"} Bad settings, using defaults {}

// Scope Inside try

try, catch, and finally are blocks, so let and const inside them stay inside them. Declare a variable before the try if you need it afterward:

let data = null;
try {
  data = JSON.parse('{"score": 92}');
} catch (error) {
  console.log("could not parse");
}
console.log(data.score);
92

// What try Cannot Catch

try...catch only catches errors thrown while its block is running. A callback that runs later, like one passed to setTimeout (JS Timers), runs after the try has already finished:

try {
  setTimeout(() => {
    null.x;                  // thrown 1 second later...
  }, 1000);
} catch (error) {
  console.log("never caught here");   // ...long after this try is done
}

Put the try...catch inside the callback instead. Debug Async covers errors in code that runs later.

// Quick Check

What does this log?

function f() {
  try {
    return "A";
  } finally {
    console.log("B");
  }
}
console.log(f());
Show answer
B then A. The return value is decided first, then finally runs (logging B) before the function actually hands back "A".

Which is the best way to report "Not found"?

  1. throw "Not found";
  2. throw 404;
  3. throw new Error("Not found");
  4. throw { msg: "Not found" };
Show answer
C. An Error object has a name, a message, and a stack showing where it happened. The others lose that information.

When does a catch block not run?

Show answer
When nothing in the try block throws. It also cannot catch syntax errors in its own script, or errors thrown later by callbacks such as setTimeout.

// Try It

  1. Click Open Playground and run it. Which blocks ran for the good input, and which for the bad one?
  2. Throw a RangeError from a function when a score is not between 0 and 100
  3. Open the checkInput example with its Try it Yourself button and extend it so it also rejects numbers with decimals
  4. Catch only SyntaxError from JSON.parse and rethrow anything else
// Video Walkthroughs
Last updated: Oct 10, 2026, 1:50 AM ET