[Issue 3613] Error Chaining in JavaScript: Using Error.cause for Clearer Debugging

Preface
This article explains how to use JavaScript's `Error.cause` property and why it’s valuable for clearer, more precise error handling.
In JavaScript, throwing errors is easy — but understanding the real root cause is often messy. The `cause` property solves this by letting us chain errors while preserving their original context.
📎 Related reading: Issue #3532 – The Evolution of Error Pages
---
Problems with Traditional Error Handling
In multilayered code — services calling services, wrapped functions, bubbled errors — it’s common to lose track of where the failure started.
Example: Old Approach
try {
JSON.parse('{ bad json }');
} catch (err) {
throw new Error('Something went wrong: ' + err.message);
}Here, once you wrap the original error, you lose:
- The original stack trace
- The specific error type
---
Introducing `Error.cause`
With the `cause` parameter, you can wrap errors without losing their original details:
try {
try {
JSON.parse('{ bad json }');
} catch (err) {
throw new Error('Something went wrong', { cause: err });
}
} catch (err) {
console.error(err.stack);
console.error('Caused by:', err.cause.stack);
}Output:
Error: Something went wrong
at ...
Caused by: SyntaxError: Unexpected token b in JSON at position 2
at JSON.parse ()
at ...✅ The top-level message stays clear,
✅ The root cause remains intact.
📎 Related: Issue #3479 – Guidelines for Error Messages in UI Design
---
Practical Example
function fetchUserData() {
try {
JSON.parse('{ broken: true }');
} catch (parseError) {
throw new Error('Failed to fetch user data', { cause: parseError });
}
}
try {
fetchUserData();
} catch (err) {
console.error(err.message); // "Failed to fetch user data"
console.error(err.cause); // SyntaxError object
console.error(err.cause instanceof SyntaxError); // true
}Key Points
- The `cause` property is non-enumerable — it won’t clutter your logs unless you explicitly print it.
- Similar behavior to `message` and `stack`.
💡 Tip: Stack traces are not automatically combined — you must manually log `err.cause.stack`.
---
Before `cause`: Workarounds
Before ES2022, developers had to:
- Concat strings into `message`
- Add custom `.originalError`
- Fully wrap errors
These were inconsistent and often overwrote metadata.
📎 Related: 【Issue 3451】Permanent Fix for Frontend `TypeError`
---
Using `cause` in Custom Errors
class DatabaseError extends Error {
constructor(message, { cause } = {}) {
super(message, { cause });
this.name = 'DatabaseError';
}
}For TypeScript:
{
"compilerOptions": {
"target": "es2022",
"lib": ["es2022"]
}
}Without this, you may get type errors when passing `{ cause }`.
---
Better Test Assertions
Error chains make tests clearer:
expect(err.cause).toBeInstanceOf(ValidationError);---
Caveats & Best Practices
- `console.error(err)` shows only the top-level error — log causes manually.
- Use `cause` sparingly — wrap errors only for meaningful context.
---
Utility: Recursively Log Error Chains
function logErrorChain(err, level = 0) {
if (!err) return;
console.error(' '.repeat(level * 2) + `${err.name}: ${err.message}`);
if (err.cause instanceof Error) {
logErrorChain(err.cause, level + 1);
} else if (err.cause) {
console.error(' '.repeat((level + 1) * 2) + String(err.cause));
}
}---
Logging the Full Stack Chain
function logFullErrorChain(err) {
let current = err;
while (current) {
console.error(current.stack);
current = current.cause instanceof Error ? current.cause : null;
}
}---
Cross-Layer Error Chain Example
Example chain:
- ConnectionTimeoutError — DB connection failed
- DatabaseError — wraps connection error
- ServiceUnavailableError — wraps database error
class ConnectionTimeoutError extends Error {}
class DatabaseError extends Error {}
class ServiceUnavailableError extends Error {}
try {
try {
try {
throw new ConnectionTimeoutError('DB connection timed out');
} catch (networkErr) {
throw new DatabaseError('Failed to connect to database', { cause: networkErr });
}
} catch (dbErr) {
throw new ServiceUnavailableError('Unable to save user data', { cause: dbErr });
}
} catch (finalErr) {
logErrorChain(finalErr);
}Console:
ServiceUnavailableError: Unable to save user data
DatabaseError: Failed to connect to database
ConnectionTimeoutError: DB connection timed out---
Browser & Runtime Support
- Browsers: Chrome 93+, Firefox 91+, Safari 15+, Edge 93+
- Node.js: 16.9+
- Others: Bun & Deno (latest)
> ⚠️ DevTools may not display `cause` automatically — log it manually.
> Babel/TypeScript will not polyfill `cause`.
---
Summary
✅ What `Error.cause` Gives You
- Preserve context with `new Error(message, { cause })`
- Works with built-in & custom errors
- Supported across modern runtimes
- Clearer logs, better debugging, stronger tests
⚠️ Remember:
- Manually log `err.cause`
- Avoid chaining trivial errors
---
Related Resource: AiToEarn
For developers sharing knowledge like this, AiToEarn官网 enables:
- AI-powered content generation
- Publishing to multiple platforms automatically
- Analytics & monetization opportunities
Also see: AiToEarn博客 | AiToEarn文档 | AI模型排名
---
About This Article
Translation of: