Error handling
try/catch/finally, throwing errors, custom error classes, error.cause and defensive code.
Things go wrong in every real program: a user types letters into a number field, a file is missing, a server returns garbage. Error handling is how your code notices those problems, reacts sensibly, and tells someone what happened — instead of crashing with a cryptic message or, worse, silently producing wrong results.
What an error looks like#
When JavaScript hits a problem it can't continue from, it throws an error. If nothing catches it, the program stops and the error is printed:
The output tells you the error type (TypeError), the message, and a stack trace showing where it happened. Learn to read the first line of the stack trace — it usually points straight at the bug.
The built-in error types you'll meet most often:
try … catch#
Wrap code that might fail in try. If it throws, execution jumps to catch, which receives the error:
Every error object has three useful properties:
err.name— the type, like"SyntaxError"err.message— the human-readable descriptionerr.stack— the stack trace as a string (non-standard but available in all engines)
If you don't need the error object, you can leave out the binding (optional catch binding):
finally: clean up no matter what#
A finally block runs after try/catch whatever happens — success, error, or even an early return:
Notice that "Hide spinner" prints before the returned value is logged: finally runs before the function actually hands back its result. Use finally for clean-up: hiding loaders, closing connections, re-enabling buttons.
Don't
returnfrom insidefinally. It silently overrides any value or error fromtry/catch, which hides bugs.
Throwing your own errors#
Use throw when your function can't do its job. Always throw an Error object (or a subclass), not a string:
Why not throw "something broke"? You can throw any value, but a string has no name, no stack and can't be checked with instanceof. Every serious codebase throws Error objects.
Custom error classes#
For your own kinds of failure, extend Error. This lets callers tell different problems apart:
The final else { throw err; } is important: only catch what you know how to handle, and rethrow the rest so real bugs aren't hidden.
Wrapping errors with cause#
Often a low-level error (like a SyntaxError from JSON.parse) isn't meaningful to the caller. Throw a clearer error and attach the original as its cause (ES2022):
You get a friendly message for the user and the technical detail for debugging. Node and browser DevTools also print the cause chain when an error is logged.
try/catch only catches synchronous errors#
try/catch only sees errors thrown while the try block is running. A callback that runs later is outside it:
The try block finished long before the timer fired. To handle errors in asynchronous code you put the try/catch inside the callback, or use promises with .catch() and async/await with try/catch — all covered in the asynchronous module.
Global safety nets#
For errors nobody catches, you can register a last-resort handler — useful for logging to a monitoring service, not for normal control flow:
In Node the equivalents are process.on("uncaughtException", …) and process.on("unhandledRejection", …). After an uncaught exception, log the error and exit: the program may be in a broken state.
Defensive code: avoid errors in the first place#
Not every problem needs try/catch. Often it's clearer to check first:
A useful rule of thumb:
- Expected situations (empty list, missing optional field) → handle with normal code.
- Invalid input or broken contracts →
throwa descriptive error, as early as possible ("fail fast"). - Operations that can fail for outside reasons (parsing user data, network, storage) →
try/catchwhere you can actually recover or show a message.
Common mistakes#
- Swallowing errors:
catch (err) {}with nothing inside hides bugs forever. At minimum log it, and usually rethrow. - Throwing strings instead of
Errorobjects — you lose the stack trace. - Wrapping everything in one giant
try. Keeptryblocks small, around the code that can actually fail, so you know what went wrong. - Forgetting
this.namein custom error classes, so logs sayErrorinstead ofValidationError. - Showing raw error messages to users.
err.messageis for developers; show users a friendly, actionable message. - Expecting
try/catchto catch errors inside callbacks that run later.
What's next#
Your programs are getting bigger. Next, learn to split code into separate files with modules: import and export.
Check your understanding
Quick quiz
1.When does the code in a
finallyblock run?2.Which is the best way to signal that a function received invalid input?
3.What is
error.causefor?
Finished reading?
Mark this lesson complete to track your progress.