Catching an error is not the same as making it disappear. A catch block that holds nothing but a polite comment turns a visible failure into inexplicable behavior, which is distinctly worse.
So the real subject of this keyword is not the syntax, which fits in three lines, but the decision: what to do with this error now that the program is holding it.
Definition
catch closes a try block and receives the object thrown by the error. The code inside runs only when something failed, and execution resumes normally right after, as though nothing had happened.
function readConfig(text) {
try {
return JSON.parse(text);
} catch (error) {
console.warn("Unreadable configuration:", error.message);
return { theme: "light" };
}
}
console.log(readConfig('{ "theme": "dark" }')); // { theme: 'dark' }
console.log(readConfig("broken")); // { theme: 'light' }From the caller's point of view this function can no longer fail: it always returns a usable object. That is the most useful shape a catch can take, the one that produces a fallback instead of passing the problem along.
What the error object holds
The parameter you receive is nearly always an instance of Error, carrying a small set of standard properties.
| Property | What it holds |
|---|---|
name | The type, for example TypeError or SyntaxError |
message | The readable sentence describing the problem |
stack | The call stack at the moment it was raised |
cause | The original error, when one was passed along |
The word nearly matters: throw accepts any value at all, including a plain string. A block that reaches for error.message without thinking will therefore print undefined now and then. A test with instanceof settles the doubt whenever the source is not your own code.
Telling several errors apart
JavaScript offers no per-type blocks, unlike some other languages. The sorting happens inside, with an ordinary condition.
try {
JSON.parse("{{{");
} catch (error) {
if (error instanceof SyntaxError) {
console.log("Invalid format");
} else {
throw error; // this block cannot handle the rest
}
}The else branch is the part people forget most often. Without it, the block silently swallows errors it does not understand, and the defect reappears three screens later in an unrecognizable form. Re-throwing whatever you cannot handle is the sensible default.
Since 2019 the parameter is optional: catch { with no parentheses is valid when the error object serves no purpose.
Frequently asked questions
What happens if the catch raises an error itself?
It leaves the structure and travels up to the enclosing block, exactly as though it had been raised outside. The matching finally still runs before that departure. This is the mechanism that lets you re-throw an enriched error without losing the cleanup you planned.
Can an error inside a promise be caught?
Yes, in two ways. With async and await, the classic structure works as it is, because waiting brings the failure back into the current thread. Without them, you go through the catch() method of the Promise. Mixing both styles on the same call buys you nothing.
Should every block write to the logs?
No, and doing so is a considerable source of noise. Log where the decision is made, once per error. If the block re-throws, it does not log: the level above will, with more context, and two lines for one incident waste time on the day you need to understand what happened.