Handbooks / JavaScript / Chapter 7
Errors and Debugging
7 pages · ~5 min✓ Reviewed
Builds on Event Loop and Async. Next up: Modules and Tooling.
Errors and Debugging Mental Model
1. Error objects and types
- An
Errorcarries name, message and stack - Any value can be thrown, but throw
Errorobjects to get a stack trace
| Type | Cause | Example |
|---|---|---|
SyntaxError | Parse time, nothing runs | let = ; |
ReferenceError | Undeclared variable | foo |
TypeError | Wrong type, calling undefined | undefined.x |
RangeError | Value out of range | new Array(-1), infinite recursion |
URIError | Bad URI function input | decodeURI('%') |
2. Where errors go
- throwruntime error
- Call stackbubbles up
- catchhandled
- No catchscript or task dies, console
- try/catch sees only synchronous throws and awaited rejections
- A throw in
setTimeoutor an un-awaited promise skips it
try { setTimeout(() => { throw new Error('late'); }, 0); } catch (e) { // never runs: the callback fires after try exits }
the error escapes to the console
3. Debugging loop
- 1Reproduce
- 2Readmessage, stack top-down
- 3Isolatebreakpoint or log
- 4Fix
- 5Re-test
try/catch/finally and Throwing Errors
1. try / catch / finally
try { risky() } catch (err) { handle(err) } finally { cleanup() }- finally runs on success, on throw, and even after
return catch { ... }is the optional binding: no error object needed- return in finally overrides the try/catch return and swallows a pending throw
function f() { try { return 'try'; } finally { console.log('cleanup'); } } console.log(f());
finally runs before the function returns
cleanup
try2. Throwing and Rethrowing
| throw new Error('msg') |
|---|
Custom Errors and error.cause
1. Define a Custom Error
extends Error, then call super(msg, opts) first- Set
this.name, or it prints as plainError - Passing
optsto super lets cause flow through
class ValidationError extends Error { constructor(msg, opts) { super(msg, opts); this.name = 'ValidationError'; this.field = 'email'; } }
extra fields: this.field, this.status = 404
- Check type:
err instanceof ValidationError - Never match
err.messagestrings - Handlers branch on fields like
err.status, no parsing
2. Wrap with error.cause
Unhandled Promise Rejections
1. When a Rejection Is Unhandled
- Rejected promise with no
.catchand noawaitin try/catch = unhandled - Browser:
window.addEventListener('unhandledrejection', fn) - Node:
process.on('unhandledRejection', fn) - Node 15+ kills the process by default; browsers only log it
2. How Errors Escape
asyncfunction always returns a promise: athrowbecomes a rejection, never a sync throw- Missing
awaitorreturndetaches the chain; outer try/catch misses it
async function save() { try { fetch('/api'); } // no await: escapes catch (e) { /* never runs */ } try { await fetch('/api'); } catch (e) { report(e); } // caught }
await keeps the rejection inside try
Console Methods, Breakpoints and DevTools
1. Console Methods
| Method | Use |
|---|---|
log / info | normal output |
warn | warning, stack trace, own filter |
error | error, stack trace, own filter |
table(arr) | grid from array of objects |
dir(obj) | object tree |
group / groupEnd | indent related logs |
time('t') / timeEnd('t') |
Source Maps and Debugging Cheat Sheet
1. What a Source Map Does
- A .map file links minified output back to original source
- Stack traces and breakpoints show your real files and lines
- Enabled by a comment at the end of the bundle
- Build tools emit it:
sourcemap: trueor webpackdevtool
//# sourceMappingURL=app.js.map // build: { sourcemap: true } // webpack: devtool: 'source-map'
2. Production and Node
| Topic | Rule |
|---|---|
| Production | Ship maps privately: upload to Sentry or use hidden source maps |
| Version |
Check yourself
What does this print, and in what order?
cleanup, thentry- finally runs before the function actually returns
function f() { try { return 'try'; } finally { console.log('cleanup'); } } console.log(f());
Why is 'caught' never printed?
- The callback runs after the try block has already exited
- Put the try/catch inside the callback
try { setTimeout(() => { throw new Error('boom'); }, 0); } catch (e) { console.log('caught'); }
What goes wrong here?
- load() returns a rejected promise, so nothing throws synchronously
- Result: unhandled rejection. Fix:
await load()inside try
async function load() { throw new Error('x'); } try { load(); } catch (e) { console.log('caught'); }