Error handling, "try...catch"
Every program eventually hits something it didn’t plan for. A typo in your own code. A user who pastes garbage into a form. A server that answers with half a response, or none at all. There are a thousand ways for a script to end up somewhere it can’t continue.
By default, JavaScript’s response to that is blunt: the script stops on the spot, and the error prints to the console. Whatever was supposed to happen next just doesn’t.
Most of the time that’s too harsh. You’d rather catch the problem, do something sensible about it, and keep going. The try...catch statement is how you buy that second chance.
The “try…catch” syntax
The construct has two blocks that work as a pair, try and catch:
try {
// code...
} catch (err) {
// error handling
}
The engine runs it in a fixed order:
- The code inside
try {...}runs first. - If nothing goes wrong, execution reaches the end of
tryand jumps straight pastcatch. Thecatchblock never runs. - If an error is thrown anywhere inside
try, the rest oftryis abandoned and control jumps tocatch (err). Theerrbinding (you can name it anything) holds an error object describing what happened.
The key point: an error inside try {...} no longer kills the script. You get a chance to deal with it in catch.
A couple of examples make the two paths concrete.
An errorless run shows alert (1) and then (2), and never touches (3):
try {
alert('Start of try runs'); // *!*(1) <--*/!*
// ...no errors here
alert('End of try runs'); // *!*(2) <--*/!*
} catch (err) {
alert('Catch is ignored, because there are no errors'); // (3)
}
A run with an error shows (1), skips (2) entirely, and lands on (3):
try {
alert('Start of try runs'); // *!*(1) <--*/!*
*!*
ghost; // error, variable is not defined!
*/!*
alert('End of try (never reached)'); // (2)
} catch (err) {
alert(`Error has occurred!`); // *!*(3) <--*/!*
}
Notice that (2) never appears in the second example. Once ghost throws, everything below it in try is skipped.
Error object
When something throws, JavaScript builds an object describing the failure and passes it to catch as the argument:
try {
// ...
} catch (err) { // <-- the "error object", could use another name instead of err
// ...
}
Every built-in error object carries two standard properties.
nameThe error’s name. For an undefined variable, that’s
"ReferenceError".
messageA human-readable description of what went wrong.
Most environments add other, non-standard properties too. The most widely supported one is:
stackThe call stack: a string listing the nested calls that led up to the error. Meant for debugging, not for showing to users.
Putting them together:
try {
ghost; // error, variable is not defined!
} catch (err) {
alert(err.name); // ReferenceError
alert(err.message); // ghost is not defined
alert(err.stack); // ReferenceError: ghost is not defined at (...call stack)
// Can also show the error as a whole.
// It converts to a string in the form "name: message"
alert(err); // ReferenceError: ghost is not defined
}
That last line is worth remembering: turning an error into a string gives you "name: message", which is often exactly what you want in a log line.
Optional “catch” binding
If you don’t plan to touch the error object, you can drop the binding entirely and write catch with no parentheses:
try {
// ...
} catch { // <-- without (err)
// ...
}
This is a small convenience for cases where you only care that something failed, not what it was. It’s a relatively recent addition to the language, so very old engines may not support it.
Using “try…catch”
Time for a realistic case. JavaScript’s JSON.parse(str) reads a JSON-encoded string and turns it into a value.
You’ll usually meet it when decoding data that arrived over the network, from a server or some other source:
let json = '{"title":"Deep Currents", "pages": 320}'; // data from the server
let book = JSON.parse(json); // convert the text into a JS object
// now book is an object with properties from the string
alert( book.title ); // Deep Currents
alert( book.pages ); // 320
There’s more on JSON in the JSON methods, toJSON chapter.
If json is malformed, JSON.parse throws, and the script dies.
Is that acceptable? Not really. If the response is broken, the visitor sees nothing at all unless they happen to open the developer console. A page that silently freezes with no message feels broken in a way that frustrates people.
Wrapping the parse in try...catch turns a dead page into a handled situation:
let json = "{ bad json }";
try {
let book = JSON.parse(json); // <-- when an error occurs...
alert( book.title ); // doesn't run
} catch (err) {
// ...control jumps here
alert( "Our apologies, the data has errors, we'll try to request it one more time." );
alert( err.name );
alert( err.message );
}
Here catch only shows a message, but you’re free to do far more from it: fire off a fresh network request, offer the visitor an alternative, ship the error to a logging service. Any of those beats letting the script fall over.
Throwing our own errors
What if the JSON is perfectly valid but missing something your code depends on, like a required title?
let json = '{ "pages": 320 }'; // incomplete data
try {
let book = JSON.parse(json); // <-- no errors
alert( book.title ); // no title!
} catch (err) {
alert( "doesn't run" );
}
JSON.parse is happy here, so catch never fires. But for your program, a book with no title is broken data. The engine has no way of knowing that’s a problem, so you have to say so yourself.
The tool for that is the throw operator. Using it lets you funnel both kinds of failure, the parse error and your own validation error, through the same catch.
“Throw” operator
throw raises an error on demand. Its syntax:
throw <error object>
Technically the operand can be any value, even a number or a string. In practice you should throw objects, ideally ones with name and message properties, so they behave like the built-in errors the rest of your code already expects.
JavaScript ships constructors for the standard error types: Error, SyntaxError, ReferenceError, TypeError, and more. You can build error objects with them:
let error = new Error(message);
// or
let error = new SyntaxError(message);
let error = new ReferenceError(message);
// ...
For these built-in errors, name comes out as the exact name of the constructor, and message is whatever string you passed in:
let error = new Error("Something broke");
alert(error.name); // Error
alert(error.message); // Something broke
To see which one JSON.parse throws, catch it and look:
try {
JSON.parse("{ oops bad json }");
} catch (err) {
alert(err.name); // SyntaxError
alert(err.message); // Unexpected token o in JSON at position 2
}
It’s a SyntaxError. So to keep our own error consistent with what a bad parse produces, we throw the same type when name is missing:
let json = '{ "pages": 320 }'; // incomplete data
try {
let book = JSON.parse(json); // <-- no errors
if (!book.title) {
throw new SyntaxError("Incomplete data: no title"); // (*)
}
alert( book.title );
} catch (err) {
alert( "JSON Error: " + err.message ); // JSON Error: Incomplete data: no title
}
At line (*), throw raises a SyntaxError carrying your message, exactly as if the engine had produced it. The rest of try is abandoned and control lands in catch.
Now catch is the single place that handles both parse errors and your own validation errors.
Rethrowing
Here’s the catch about catch: it grabs every error from try, not only the ones you were expecting. What if an unrelated bug slips in, a genuine programming mistake rather than the bad-data case you had in mind?
let json = '{ "pages": 320 }'; // incomplete data
try {
book = JSON.parse(json); // <-- forgot to write "let" before book
// ...
} catch (err) {
alert("JSON Error: " + err); // JSON Error: ReferenceError: book is not defined
// (not actually a JSON error!)
}
Bugs happen. Even code that millions have relied on for years turns out to hide one now and then, sometimes a serious one.
The problem above is that the catch was written to explain “bad JSON”, but it dutifully reports a ReferenceError the same way. The label is a lie, and it sends anyone debugging in the wrong direction.
The fix is a discipline called rethrowing, and the rule is short:
A catch block should handle only the errors it recognizes, and rethrow the rest.
Spelled out as steps:
catchreceives every error.- Inside
catch (err) {...}, you inspecterr. - If it’s not something you know how to handle, you
throw erragain to pass it up.
The usual way to check the type is the instanceof operator:
try {
book = { /*...*/ };
} catch (err) {
if (err instanceof ReferenceError) {
alert('ReferenceError'); // "ReferenceError" for accessing an undefined variable
}
}
You can also read the class name from err.name, which every native error sets, or from err.constructor.name.
Rewriting the JSON example so catch only owns SyntaxError and lets everything else escape:
let json = '{ "pages": 320 }'; // incomplete data
try {
let book = JSON.parse(json);
if (!book.title) {
throw new SyntaxError("Incomplete data: no title");
}
blabla(); // unexpected error
alert( book.title );
} catch (err) {
if (err instanceof SyntaxError) {
alert( "JSON Error: " + err.message );
} else {
throw err; // rethrow (*)
}
}
The throw err on line (*) “falls out” of this try...catch. From there it’s either caught by an enclosing try...catch, if there is one, or it kills the script like any uncaught error. So this catch handles what it understands and quietly steps aside for the rest.
An enclosing level shows how the escaped error gets caught one layer up:
function readData() {
let json = '{ "pages": 320 }';
try {
// ...
blabla(); // error!
} catch (err) {
// ...
if (!(err instanceof SyntaxError)) {
throw err; // rethrow (don't know how to deal with it)
}
}
}
try {
readData();
} catch (err) {
alert( "External catch got: " + err ); // caught it!
}
readData deals with SyntaxError and nothing else. The outer try...catch is the safety net for everything that gets past it.
try…catch…finally
There’s one more clause. try...catch can end with a finally block, and its defining trait is that it runs no matter what:
- after
trywhen there was no error, - after
catchwhen an error was handled.
The full shape:
try {
... try to execute the code ...
} catch (err) {
... handle errors ...
} finally {
... execute always ...
}
Try this one:
try {
alert( 'try' );
if (confirm('Make an error?')) BAD_CODE();
} catch (err) {
alert( 'catch' );
} finally {
alert( 'finally' );
}
It has two possible routes:
- Answer “Yes” to “Make an error?” and you get
try→catch→finally. - Answer “No” and you get
try→finally.
finally earns its place when you start something that must be cleaned up regardless of how the block ends.
Say you want to time how long a stair-climbing counter climbWays(n) takes. It counts the distinct ways to climb n steps taking one or two at a time, and it does that with a naive double recursion, so a big n is genuinely slow. You grab a timestamp before, another after, and subtract. But what if the call throws partway through? The climbWays below rejects negative or non-integer input, so that’s a real possibility. finally is the right home for the “stop the clock” step, because it runs whether climbWays returns cleanly or blows up:
let num = +prompt("Enter a positive integer number?", 35)
let diff, result;
function climbWays(n) {
if (n < 0 || Math.trunc(n) != n) {
throw new Error("Must not be negative, and also an integer.");
}
return n <= 1 ? 1 : climbWays(n - 1) + climbWays(n - 2);
}
let start = Date.now();
try {
result = climbWays(num);
} catch (err) {
result = 0;
} finally {
diff = Date.now() - start;
}
alert(result || "error occurred");
alert( `execution took ${diff}ms` );
Enter 35 and it runs to completion: try finishes, then finally records the elapsed time. Enter -1 and climbWays throws right away, catch sets result to 0, and finally still records the timing (about 0ms). Both outcomes measure correctly, because the “finish” step lives where it can’t be skipped.
Whether the function leaves via return or throw makes no difference to finally. It runs either way.
Global catch
Suppose a fatal error strikes outside any try...catch and the script dies, maybe a plain programming bug or something worse. Can you react to that at all? You might want to log it, or show the user a graceful message instead of a frozen page.
The specification doesn’t define a mechanism, but environments provide one because it’s too useful to skip. Node.js has an uncaughtException event. In the browser you assign a function to the window.onerror property, and it runs whenever an error goes uncaught.
The signature:
window.onerror = function(message, url, line, col, error) {
// ...
};
messageThe error message.
urlURL of the script where the error happened.
line,colLine and column numbers of the error.
errorThe error object itself.
For example:
<script>
window.onerror = function(message, url, line, col, error) {
alert(`${message}\n At ${line}:${col} of ${url}`);
};
function readData() {
badFunc(); // Whoops, something went wrong!
}
readData();
</script>
A global handler like window.onerror usually isn’t meant to recover the script. After a programming error the state is often too broken to trust. Its real job is to report the failure to you, the developer, so you learn it happened at all.
Several services specialize in exactly this kind of error logging, such as https://sentry.io. The pattern is always similar:
- You register, and the service gives you a snippet of JS (or a script URL) to add to your pages.
- That script installs its own
window.onerrorhandler. - When an error fires, the handler sends a request describing it to the service.
- You log into the service’s dashboard and review what broke, for whom, and when.
Summary
try...catch lets you handle runtime errors. You “try” a block of code and “catch” any exception it throws, instead of letting it stop the script.
The full syntax:
try {
// run this code
} catch (err) {
// if an error happened, jump here
// err is the error object
} finally {
// run in any case, after try/catch
}
Either catch or finally may be omitted, so try...catch and try...finally are both valid on their own.
Error objects carry these properties:
message— the human-readable description.name— the error’s name, which matches its constructor.stack— non-standard but well supported, the call stack captured when the error was created.
Drop the binding with catch { in place of catch (err) { when you don’t need the object.
You raise your own errors with throw. Its operand can technically be any value, but in practice it should be an error object, usually one built on the standard Error class. Extending that class comes in the next chapter.
Rethrowing is a core habit: a catch block knows how to deal with a specific kind of error, so it should rethrow anything else rather than swallow it under a misleading message.
And even without try...catch, most environments give you a global handler for errors that escape everything. In the browser that’s window.onerror.