Error Handling
What Happens When Code Throws
Before learning to handle errors, see exactly what an unhandled error looks like:
When an error is thrown and nothing catches it, execution stops immediately. Every line after the throw is skipped. In a real application this means a crashed page, a failed request, a lost transaction. Error handling is how you take back control.
try / catch — The Basic Pattern
The try block is the code you want to run. The catch block runs only if something inside try throws. After either path, execution continues normally. The error does not propagate, the program does not crash.
err is a real object — it has .message, .name, and .stack. You are not just catching a string.
finally — Code That Always Runs
finally runs whether the try succeeded, threw, or the catch itself threw. It is guaranteed:
finally is for cleanup that must happen regardless of outcome — closing a file, releasing a lock, hiding a loading spinner. It is not optional cleanup — it is guaranteed cleanup.
The Error Object in Detail
JavaScript has several built-in error types, each with a specific meaning:
instanceof lets you catch different error types and respond differently — the same way you would handle a network timeout differently from a permission error.
Custom Error Classes
Built-in error types are too generic for real applications. Define your own so errors carry meaning specific to your domain:
Custom errors carry extra fields (field, resource, id) so catching code has all the context it needs to respond intelligently — log it, show a user message, retry, or escalate.
Re-throwing — Catching Only What You Can Handle
A common mistake: a catch block swallows errors it doesn't understand. Always re-throw what you cannot handle:
Catch blocks that silently swallow every error are one of the hardest bugs to track down. If you do not know what to do with an error, re-throw it and let something higher up decide.
Error Handling in Async Code
Errors in promises and async/await follow the same logic — they just travel through promise chains instead of call stacks:
try/catch wraps await exactly as it wraps synchronous code. The error that was rejected in the promise surfaces as a thrown error inside the async function. Same mental model, same tools.
A Robust Function — Putting It All Together
This is what error handling looks like in production code — validate inputs explicitly, throw typed errors with meaningful messages, catch them where you can respond appropriately, re-throw what you cannot.
What You Have Learned
Errors are not exceptions to normal programming — they are a normal part of it. The question is always whether you handle them intentionally or let them handle you.
The key ideas:
- An unhandled throw stops execution immediately — everything after it is skipped
try/catchcatches thrown errors and keeps the program runningfinallyruns regardless of outcome — guaranteed cleanup- Built-in error types (
TypeError,RangeError, etc.) carry specific meaning — useinstanceofto distinguish them - Custom error classes extend
Errorand carry domain-specific context — prefer these in any real application - Re-throw errors you cannot handle — never silently swallow what you don't understand
try/catchwrapsawaitexactly as it wraps synchronous code — same tool, same mental model
In the next lesson: Modules — how to split JavaScript across multiple files, export what others need, import what you need, and build a codebase that scales without collapsing into a single enormous file.