⚠️ JavaScript Error Handling — try/catch, Custom Errors & Async Errors
Robust error handling in JavaScript — try/catch/finally, throw, custom Error classes, async error patterns and how to fail gracefully. With code examples and interview Q&A.
Things break. Networks fail. Users type weird input. Robust JavaScript expects failure and handles it gracefully.
🛡️ try/catch/finally
try {
doRiskyThing();
} catch (err) {
console.error(err.message);
} finally {
cleanup(); // always runs
}🚨 Throwing errors
function divide(a, b) {
if (b === 0) throw new Error('Divide by zero');
return a / b;
}🏷️ Custom Error classes
class ValidationError extends Error {
constructor(message, field) {
super(message);
this.name = 'ValidationError';
this.field = field;
}
}🌊 Async errors
A rejected Promise inside await behaves exactly like a thrown error — try/catch catches both.
try {
const data = await fetch('/api').then(r => r.json());
} catch (err) {
// Network failure OR JSON parse failure
}💡 Fail loud OR fail safe — never silently
An empty catch (e) {} is almost always a bug. At minimum, log it.
💻 Code Examples
finally always runs
function test() {
try {
return 'a';
} finally {
console.log('cleanup');
}
}
console.log(test());Output:
cleanup a
Catching specific error types
try {
validateUser(data);
} catch (err) {
if (err instanceof ValidationError) {
showFieldError(err.field, err.message);
} else {
throw err; // rethrow unknown
}
}Output:
Handle expected errors, rethrow surprises.
⚠️ Common Mistakes
- Swallowing errors with empty catch blocks — bugs become invisible.
- Throwing strings instead of Error objects — you lose the stack trace. Always `throw new Error(...)`.
- Forgetting that try/catch doesn't catch errors inside setTimeout callbacks or unhandled Promises.
- Catching too broadly when you only meant to handle one specific case.
🎯 Interview Questions
Real questions asked at top product and service-based companies.
Q1.What does the finally block do?Beginner
It runs whether the try block succeeded or threw. Used for cleanup — closing files, hiding loaders, releasing locks. It runs even if you `return` from try or catch.
Q2.Can try/catch catch errors from async code?Intermediate
It catches synchronous errors AND awaited Promise rejections. It does NOT catch errors inside callbacks (setTimeout, event listeners) or un-awaited Promises — those need their own handling.
Q3.Why throw an Error object instead of a string?Intermediate
Error objects carry a stack trace, a name, and can be extended (custom error classes). Strings can be thrown but lose all that context — making debugging much harder.
Q4.How do you handle an unhandled Promise rejection globally?Advanced
Browsers: `window.addEventListener('unhandledrejection', e => …)`. Node.js: `process.on('unhandledRejection', …)`. Use it to log/telemetry — but fix the root cause, don't rely on it.
Q5.Why might you create a custom Error class?Intermediate
To differentiate error types (`if (e instanceof NetworkError)`), attach extra data (status codes, validation fields) and produce cleaner stack traces. Makes higher-level handlers smarter.
🧠 Quick Summary
- try/catch/finally handles sync errors and awaited rejections.
- Always throw Error objects, never strings.
- Use custom Error subclasses for typed handling.
- finally runs no matter what — perfect for cleanup.
- Listen to unhandledrejection / uncaughtException for last-resort logging.