try...catch...finally statement for synchronous errors and special patterns for handling async errors in Promises and async/await.
- The
try...catch...finallystatement and when to use each block - The Error object and its properties (name, message, stack)
- Built-in Error types: TypeError, ReferenceError, SyntaxError, and more
- How to throw your own errors with meaningful messages
- Creating custom Error classes for better error categorization
- Error handling patterns for async code
- Global error handlers for catching uncaught errors
- Common mistakes and real-world patterns
What is Error Handling in JavaScript?
Errors happen. Users enter invalid data, network requests fail, APIs return unexpected responses, and sometimes we just make typos. Error handling is your strategy for detecting, responding to, and recovering from these problems gracefully. In JavaScript, you use thetry...catch statement to catch errors, the throw statement to create them, and the Error object to describe what went wrong. According to the Stack Overflow 2023 Developer Survey, debugging and error handling remain among the most time-consuming aspects of development, making robust error handling patterns a critical skill.
Error — MDN
try...catch — MDN
The Safety Net Analogy
Think of error handling like a trapeze act at a circus. The acrobat (your code) performs risky moves high above the ground. The safety net (your catch block) is there to catch them if they fall. And no matter what happens, the show must go on (your finally block).| Circus | JavaScript | Purpose |
|---|---|---|
| Trapeze act | try block | Code that might fail |
| Safety net | catch block | Handles the error if one occurs |
| Show continues | finally block | Cleanup that always runs |
| Acrobat falls | Error is thrown | Something went wrong |
The try/catch/finally Statement
Thetry...catch statement is JavaScript’s primary tool for handling errors. As MDN documents, this statement has been part of JavaScript since ECMAScript 3 (1999) and remains the standard mechanism for synchronous error handling. Here’s the full syntax:
The try Block
Thetry block contains code that might throw an error. If an error occurs, execution immediately jumps to the catch block.
The catch Block
Thecatch block receives the error object and handles it. This is where you log errors, show user messages, or attempt recovery.
The finally Block
Thefinally block always runs, whether an error occurred or not. It’s perfect for cleanup code like closing connections or hiding loading spinners.
try/catch Only Works Synchronously
This trips people up:try/catch won’t catch errors in callbacks that run later.
The Error Object
When an error occurs, JavaScript creates an Error object with information about what went wrong.Error Properties
| Property | Description | Example |
|---|---|---|
name | The type of error | "TypeError", "ReferenceError" |
message | Human-readable description | "Cannot read property 'x' of undefined" |
stack | Call stack when error occurred (non-standard but widely supported) | Shows file names, line numbers |
cause | Original error (ES2022+) | Used for error chaining |
stack property is essential for debugging. It shows exactly where the error occurred and the chain of function calls that led to it.
Built-in Error Types
JavaScript has several built-in error types. Knowing them helps you understand what went wrong and how to fix it. The ECMAScript specification defines seven native error types, each representing a different category of runtime problem.| Error Type | When It Occurs | Common Cause |
|---|---|---|
| Error | Generic error | Base class, used for custom errors |
| TypeError | Wrong type | null.foo, calling non-function |
| ReferenceError | Invalid reference | Using undefined variable |
| SyntaxError | Invalid syntax | Bad JSON, missing brackets |
| RangeError | Value out of range | new Array(-1) |
| URIError | Bad URI encoding | decodeURIComponent('%') |
| AggregateError | Multiple errors | Promise.any() all reject |
TypeError - The most common error
TypeError - The most common error
null or undefined:ReferenceError - Variable doesn't exist
ReferenceError - Variable doesn't exist
SyntaxError - Invalid code or JSON
SyntaxError - Invalid code or JSON
try/catch only catches runtime SyntaxErrors like invalid JSON.RangeError - Value out of bounds
RangeError - Value out of bounds
The throw Statement
Thethrow statement lets you create your own errors. When you throw, execution stops and jumps to the nearest catch block.
Always Throw Error Objects
Technically you can throw anything, but always throw Error objects. They include a stack trace for debugging.Creating Meaningful Error Messages
Good error messages tell you what went wrong and ideally how to fix it:Custom Error Classes
For larger applications, create custom error classes to categorize errors and add extra information.The Auto-Naming Pattern
Instead of manually settingthis.name in every class, use the constructor name:
Using instanceof for Error Handling
Custom errors let you handle different error types differently:Error Chaining with cause (ES2022+)
When catching and re-throwing errors, preserve the original error using thecause option:
Async Error Handling
Error handling works differently with asynchronous code. Here’s a quick overview. For comprehensive coverage, see our Promises and async/await guides.With Promises: .catch()
Use.catch() to handle errors in Promise chains:
With async/await: try/catch
With async/await, use regular try/catch blocks:The fetch() Trap: Check response.ok
This catches many developers off guard:fetch() doesn’t throw on HTTP errors like 404 or 500. It only throws on network failures.
Global Error Handlers
Global error handlers catch errors that slip through your try/catch blocks. They’re a safety net of last resort, not a replacement for proper error handling.window.onerror - Synchronous Errors
Catches uncaught errors in the browser:unhandledrejection - Promise Rejections
Catches unhandled Promise rejections:Common Mistakes
Mistake 1: Empty catch Blocks (Swallowing Errors)
Mistake 2: Catching Too Broadly
Mistake 3: Throwing Strings Instead of Errors
Mistake 4: Not Re-throwing When Needed
Mistake 5: Forgetting try/catch is Synchronous
Real-World Patterns
Retry Pattern
Automatically retry failed operations, useful for flaky network requests:Validation Error Pattern
Collect multiple validation errors at once:Graceful Degradation
Try the ideal path, fall back to alternatives:Key Takeaways
- Use try/catch for synchronous code — Wrap risky operations and handle errors appropriately
-
try/catch is synchronous — It won’t catch errors in callbacks. Use
.catch()for Promises or try/catch inside async functions - Always throw Error objects, not strings — Error objects include stack traces that are essential for debugging
-
Always check response.ok with fetch —
fetch()doesn’t throw on HTTP errors like 404 or 500 - Create custom Error classes — They help categorize errors and add context for better handling
- Use finally for cleanup — Code in finally always runs, perfect for hiding spinners or closing connections
- Don’t swallow errors — Empty catch blocks hide bugs. Always log or re-throw
- Use error.cause for chaining — Preserve original errors when wrapping them with more context
- Re-throw errors you can’t handle — If you catch an error you didn’t expect, re-throw it
- Use global handlers as a safety net — They’re for logging and tracking, not for regular error handling
Test Your Knowledge
Question 1: What's the difference between try/catch and Promise .catch()?
Question 1: What's the difference between try/catch and Promise .catch()?
try/catch only catches synchronous errors. If you have async code inside the try block (like setTimeout callbacks), errors won’t be caught.Promise .catch() catches Promise rejections, which are async. With async/await, you can use try/catch because await converts rejections to thrown errors.Question 2: Why doesn't fetch() throw on 404 or 500 errors?
Question 2: Why doesn't fetch() throw on 404 or 500 errors?
fetch() only throws on network failures (can’t reach the server). HTTP errors like 404 (Not Found) or 500 (Server Error) are valid HTTP responses, so fetch() resolves successfully.You must check response.ok to detect HTTP errors:Question 3: Why should you throw Error objects instead of strings?
Question 3: Why should you throw Error objects instead of strings?
Question 4: What does the finally block do?
Question 4: What does the finally block do?
finally block always runs, whether an error occurred or not, and even if there’s a return statement in try or catch. It’s ideal for cleanup code.Question 5: How do you handle different error types differently?
Question 5: How do you handle different error types differently?
instanceof to check the error type, or check error.name:Question 6: What's wrong with this code?
Question 6: What's wrong with this code?
result is scoped to the try block. It doesn’t exist outside of it, so console.log(result) throws a ReferenceError.Fix: Declare the variable outside the try block:Frequently Asked Questions
What is the difference between throw and return in JavaScript?
What is the difference between throw and return in JavaScript?
return ends a function and passes a value to the caller. throw creates an error that unwinds the call stack until a catch block handles it. Use throw for exceptional conditions that the current function cannot resolve; use return for normal control flow, including returning error indicators like null or result objects.Should I wrap every function in try/catch?
Should I wrap every function in try/catch?
try/catch around code that can fail unpredictably — JSON parsing, network requests, file operations, or third-party library calls. Wrapping everything adds noise and hides bugs. As MDN recommends, handle errors at the appropriate level where you have enough context to recover meaningfully.What are the built-in Error types in JavaScript?
What are the built-in Error types in JavaScript?
TypeError (wrong type), ReferenceError (undefined variable), SyntaxError (invalid syntax), RangeError (value out of range), URIError (bad URI encoding), EvalError (eval-related), and AggregateError (multiple errors, ES2021). TypeError and ReferenceError are by far the most common in practice.How do I handle errors in async/await code?
How do I handle errors in async/await code?
await calls in try/catch blocks, just like synchronous code. Unhandled promise rejections can crash Node.js processes — since Node 15, unhandled rejections terminate the process by default. For multiple parallel promises, use Promise.allSettled() to capture both successes and failures without short-circuiting.When should I create custom Error classes?
When should I create custom Error classes?
ValidationError, NotFoundError, and AuthenticationError let callers handle each case differently. Extend the built-in Error class and set a descriptive name property so stack traces remain informative.