This section explains how JavaScript represents, throws, propagates, and handles errors at runtime - synchronously and asynchronously. The goal is to build a predictive model of failure paths, not just to memorize try/catch syntax.
The exercises are designed to surface common mistakes: swallowed errors, broken async error handling, and incorrect assumptions about propagation.
Production bugs often come from error handling, not business logic:
- Errors swallowed silently
- Async failures never caught
- Incorrect retries or partial state updates
If you can answer “where does this error go?”, you can design reliable systems.
In JavaScript, errors are objects.
const err = new Error('boom');Key properties:
namemessagestack(non-standard but widely supported)
Throwing an error interrupts execution immediately.
throw new Error('invalid state');Rules:
- You can throw any value (but should not)
- Best practice: throw
Erroror subclasses
throw 'boom'; // valid, but bad practiceErrors propagate up the call stack until caught.
function a() { b(); }
function b() { throw new Error(); }
a();If uncaught, the program terminates.
try {
risky();
} catch (err) {
handle(err);
}Rules:
- Only catches synchronous errors inside the block
- Does not catch async errors unless awaited
try {
return 1;
} finally {
cleanup();
}- Always runs
- Even if an error is thrown or returned
Used for cleanup, not control flow.
If you catch an error but cannot handle it, rethrow:
catch (err) {
log(err);
throw err;
}Failing to rethrow swallows the error.
Use subclasses to encode intent:
class ValidationError extends Error {}This allows callers to distinguish failure modes.
Promise errors propagate through the chain:
Promise.resolve()
.then(() => { throw new Error(); })
.catch(err => {});Rules:
- Throwing inside
.thenrejects the next promise .catchhandles rejections
await converts rejections into thrown errors:
async function fn() {
await Promise.reject(new Error()); // throws
}Use try/catch around await.
try {
setTimeout(() => { throw new Error(); }, 0);
} catch {}The error occurs in a different call stack.
Async callbacks must handle their own errors.
Errors should represent exceptional conditions, not normal logic.
Avoid:
try { parse(); } catch { fallback(); }Prefer explicit checks when failure is expected.
The exercises rely on understanding:
- Stack-based propagation
- Rethrowing vs swallowing
- Sync vs async error handling
- Promise rejection flow
If behavior surprises you, ask:
Where does this error propagate to?
These exercises test whether you can:
- Handle errors without hiding them
- Preserve error semantics
- Reason about async failure paths
If you can explain why an error is caught (or not), you understand JavaScript error handling.