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.
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);
The second time, "end of try" never prints: execution jumped straight from the failing line into catch.
catch { ... } (added in 2019).
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");
finally even runs when try returns early:
function check() {
try {
return "from try";
} finally {
console.log("finally still runs");
}
}
console.log(check());
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 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);
}
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);
}
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("...").
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));
}
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.
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.
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"));
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);
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.
What does this log?
function f() {
try {
return "A";
} finally {
console.log("B");
}
}
console.log(f());
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"?
throw "Not found";throw 404;throw new Error("Not found");throw { msg: "Not found" };name, a message, and a stack showing where it happened. The others lose that information.When does a catch block not run?
try block throws. It also cannot catch syntax errors in its own script, or errors thrown later by callbacks such as setTimeout.RangeError from a function when a score is not between 0 and 100checkInput example with its Try it Yourself button and extend it so it also rejects numbers with decimalsSyntaxError from JSON.parse and rethrow anything else