JavaScript - Error Handling
Overview
Estimated time: 30–40 minutes
Learn practical error handling: throwing and catching errors, adding context with cause
, defining custom error types, handling async errors with promises and async/await
, and using aggregation helpers.
Learning Objectives
- Use
try/catch/finally
andthrow
effectively. - Create custom error classes and attach context via
cause
. - Handle async errors in promise chains and
async/await
. - Apply
Promise.allSettled
and understandAggregateError
fromPromise.any
.
Prerequisites
try/catch/finally and throw
function parseJSON(s) {
try {
return JSON.parse(s);
} catch (e) {
// Add context, rethrow
throw new Error('Invalid JSON input', { cause: e });
} finally {
// Optional cleanup
}
}
// parseJSON('not json'); // throws Error with cause
Custom error classes
class ValidationError extends Error {
constructor(message, { field, cause } = {}) {
super(message, { cause });
this.name = 'ValidationError';
this.field = field;
}
}
function requirePositive(n) {
if (!(Number.isFinite(n) && n > 0)) {
throw new ValidationError('Expected positive number', { field: 'n' });
}
return n;
}
Async errors: promises and async/await
// Promise chain: use .catch at the end
const delay = (ms, v, fail=false) => new Promise((res, rej) => setTimeout(() => fail ? rej(new Error('boom')) : res(v), ms));
delay(20, 1)
.then(x => x * 2)
.then(x => { if (x > 1) throw new Error('too big'); return x; })
.catch(err => { console.log('caught:', err.message); return 0; })
.then(v => console.log('recovered:', v));
// async/await with try/catch
async function run() {
try {
const a = await delay(10, 'A');
const b = await delay(10, 'B', true); // will reject
return a + b;
} catch (e) {
return 'fallback';
}
}
Aggregation helpers
// Collect results regardless of failures
const tasks = [delay(10, 'A'), delay(5, null, true), delay(8, 'C')];
const settled = await Promise.allSettled(tasks);
// settled: [{status:'fulfilled', value:'A'}, {status:'rejected', reason:Error}, {status:'fulfilled', value:'C'}]
// any: first fulfilled, otherwise throws AggregateError
try {
const v = await Promise.any([delay(5, null, true), delay(6, 'OK')]);
// v = 'OK'
} catch (e) {
if (e instanceof AggregateError) {
console.log('all failed:', e.errors);
}
}
Best practices
- Catch only where you can handle or add meaningful context; otherwise, rethrow.
- Wrap external boundaries (I/O, parsing) to attach user-friendly messages and
cause
. - Prefer
allSettled
when partial success is acceptable; preferany
when one success suffices.
Common Pitfalls
- Swallowing errors: Returning from
catch
hides failures; rethrow or log appropriately. - Unhandled rejections: Always terminate promise chains with
.catch
or wrap intry/catch
forawait
. - Losing stack/context: Use
new Error(message, { cause })
to preserve the underlying reason. - Finally assumptions:
finally
runs regardless of success/failure—avoid relying on returned values insidefinally
.
Try it
Run to see custom errors, cause, async try/catch, and aggregation: