Suggest an editImprove this articleRefine the answer for “Error handling: try/catch/finally in JavaScript”. Your changes go to moderation before they’re published.Approval requiredContentWhat you’re changing🇺🇸EN🇺🇦UAPreviewTitle (EN)Short answer (EN)**`try/catch/finally`** is JavaScript's built-in mechanism for handling runtime errors. The `try` block runs code that might fail, `catch` handles any thrown error, and `finally` runs cleanup code regardless of the outcome. ```javascript try { const data = JSON.parse('bad json'); // Throws SyntaxError } catch (error) { console.log(error.message); // "Unexpected token..." } finally { console.log('Always runs'); // Even after catch } ``` **Key:** `finally` executes even if `catch` returns early or re-throws, making it the right place for cleanup like closing connections or stopping loaders.Shown above the full answer for quick recall.Answer (EN)Image**`try/catch/finally`** is a JavaScript construct that lets code attempt a risky operation, handle any thrown error, and run cleanup code no matter what happens. ## Theory ### TL;DR - Analogy: surgeon with a backup plan. `try` is the operation, `catch` handles complications, `finally` closes the patient up either way - `finally` always runs, even if `catch` re-throws or `return` exits early - Wrap API calls, `JSON.parse`, file reads: anything that can fail at runtime - Never use `try/catch` for control flow. That is what `if/else` is for ### Quick example ```javascript try { const data = JSON.parse('{ invalid }'); // Throws SyntaxError console.log(data); // Skipped } catch (error) { console.log('Caught:', error.message); // "Unexpected token..." } finally { console.log('Cleanup done'); // Always prints } // Output: // Caught: Unexpected token i in JSON at position 2 // Cleanup done ``` When `JSON.parse` throws, execution jumps straight to `catch`. Code after the throw inside `try` is skipped. Then `finally` runs. ### What finally actually does `finally` runs after `try/catch` completes, no matter the outcome: success, caught error, or re-thrown error. Without it, you would need to duplicate cleanup code in both the `try` path and the `catch` path. One block covers both. This matters most for resource cleanup: closing database connections, stopping loading spinners, releasing file handles. If `catch` returns early, `finally` still fires before the function actually returns. ### When to use - Fetch might fail due to network issues or bad status codes: wrap in `try/catch`, show a user-friendly message - `JSON.parse` on user input: catch `SyntaxError`, fall back to default data - File reads in Node.js: `finally` closes the stream even if reading fails - `async/await` functions: `try/catch` is the clean way to handle rejected promises - Skip it for predictable paths. Use `if/else` to check conditions you control ### How V8 handles this During compilation, V8 wraps the `try` block in an exception handler and builds an internal exception table. When a `throw` happens, the engine unwinds the call stack and jumps to `catch`. After `catch` runs, `finally` executes in the same lexical scope as `try`. In browsers, unhandled errors also fire `window.onerror`. In Node.js, they emit `uncaughtException`. ### Common mistakes **1. Assuming finally skips on return** ```javascript function test() { try { return 'success'; } finally { console.log('This runs before return'); } } console.log(test()); // "This runs before return" // "success" ``` `finally` fires before the actual return. But if `finally` has its own `return`, it overrides the value from `try`. Design `finally` for side effects only, not return values. **2. Catching everything silently** ```javascript // Bad: swallows real bugs try { JSON.parse('bad'); } catch (e) {} // Better: check the type try { JSON.parse('bad'); } catch (error) { if (error instanceof SyntaxError) { console.warn('Invalid JSON, using defaults'); } else { throw error; // Re-throw anything unexpected } } ``` A `TypeError` caused by a coding mistake looks the same as a `SyntaxError` from bad input. Catch-all blocks hide both equally well. **3. Forgetting finally in Node.js resource cleanup** ```javascript // Risk: stream left open after error const stream = fs.createReadStream('file.txt'); try { // read data } catch (error) { console.error(error); } // Correct try { // read data } catch (error) { console.error(error); } finally { stream.destroy(); // Runs no matter what } ``` The most common issue I have seen in Node.js codebases is this exact pattern in DB route handlers. One failed query leaves a connection open. Enough of those and the pool exhausts completely. **4. Using try/catch for control flow** ```javascript // Slow and misleading try { riskyOperation(); } catch { fallback(); } // If you can check first, do it if (canRun()) { riskyOperation(); } else { fallback(); } ``` Exceptions carry overhead in V8. Use them for actual unexpected failures, not expected branching. ### Real-world usage - Express.js: async route handlers wrapped in `try/catch`, unknown errors passed to `next(error)` for global middleware - React: `useEffect` with fetch wrapped in `try/catch`, `AbortController` cleanup in `finally` - Node.js fs/promises: `readFile` with `finally` to close streams - Axios interceptors: `try/catch` inside request and response transformers for validation ### Follow-up questions **Q:** What happens if `finally` has a `return` statement? **A:** It overrides the return value from `try` or `catch`. The earlier return is discarded. This trips up a lot of utility function authors. **Q:** Does `try/catch` catch errors inside async callbacks like `setTimeout`? **A:** No. A `try/catch` around `setTimeout(() => { throw new Error() })` will not catch the error because the callback runs in a different execution context. Put `try/catch` inside the callback, or switch to async/await. **Q:** Can you write `try/finally` without `catch`? **A:** Yes. The error still propagates up the stack, but `finally` runs first. Useful when you want cleanup without handling the error at this level. **Q:** What is the difference between `.catch()` on a Promise and `try/catch` in an async function? **A:** Both catch rejected promises. `try/catch` in async functions is often cleaner for sequential async code. `.catch()` fits better for inline handling in promise chains. **Q:** (Senior) How does re-throwing interact with finally? **A:** Re-throwing in `catch` does not skip `finally`. Stack unwinding only completes after `finally` finishes. So `finally` always runs, then the re-thrown error propagates to the outer scope. ## Examples ### Parsing user input with fallback ```javascript function parseConfig(input) { try { const config = JSON.parse(input); // Throws SyntaxError on bad input return config; } catch (error) { if (error instanceof SyntaxError) { console.warn('Bad config, using defaults'); return { theme: 'light', lang: 'en' }; // Fallback value } throw error; // Re-throw non-syntax errors } } console.log(parseConfig('{ "theme": "dark" }')); // { theme: "dark" } console.log(parseConfig('not json')); // { theme: "light", lang: "en" } ``` The function handles `SyntaxError` specifically and returns a fallback. Anything else gets re-thrown so bugs do not vanish silently. ### Express route with database connection cleanup ```javascript app.get('/user/:id', async (req, res, next) => { try { const user = await db.getUser(req.params.id); // Throws on invalid ID res.json(user); } catch (error) { if (error.name === 'NotFoundError') { res.status(404).json({ error: 'User not found' }); } else { next(error); // Pass to Express global error handler } } finally { await db.releaseConnection(); // Always frees the connection } }); ``` Even if `db.getUser` throws or `next(error)` is called, the connection gets released. Without `finally`, a failed request leaks a connection from the pool. ### Re-throw behavior with async/await ```javascript async function risky() { try { throw new Error('Boom'); } catch (error) { console.log('Caught:', error.message); // "Boom" throw error; // Re-throw } finally { console.log('Finally runs'); // Runs even on re-throw } } risky().catch(e => console.log('Outer:', e.message)); // Output: // Caught: Boom // Finally runs // Outer: Boom ``` Many developers expect re-throwing to skip `finally`. It does not. `finally` always completes before the error continues propagating outward.For the reviewerNote to the moderator (optional)Visible only to the moderator. Helps review go faster.