JavaScript Error Handling: try/catch, Custom Errors, and Patterns
A deep, practical guide to error handling in JavaScript for frontend interviews: sync vs async errors, try/catch with async/await, custom error classes, cause, and user-friendly error patterns.
On this page
Error handling is a “senior signal” topic.
30‑second interview answer
try/catch catches synchronous errors. For async, either await inside try/catch or attach .catch() to the promise. In real apps, create meaningful error types (custom Error classes), keep user-facing messages safe, and log enough context without leaking secrets.
Key points
- Sync throw → try/catch.
- Promise rejection →
await+ try/catch OR.catch. - Prefer typed errors with
name/message/cause. - Separate user message from debug details.
Interviewers want to see if you can:
- catch errors correctly (sync vs async)
- create meaningful error types
- build user-friendly fallback UI
- log without leaking sensitive data
1) The basics: throw, try/catch
function parseJson(str) {
try {
return JSON.parse(str);
} catch (err) {
return null;
}
}Key idea:
try/catchcatches synchronous errors thrown inside the try block.
2) Async errors: Promises vs try/catch
A) Promise style
fetch('/api')
.then((r) => r.json())
.catch((err) => {
console.error('Request failed', err);
});B) async/await style
async function load() {
try {
const res = await fetch('/api');
if (!res.ok) throw new Error('Bad response');
return await res.json();
} catch (err) {
console.error(err);
throw err;
}
}Interview tip:
try/catchworks withawaitbecause the awaited promise rejection behaves like a thrown error.
3) Common mistake: try/catch doesn’t catch async without await
try {
fetch('/api').then(() => {
throw new Error('boom');
});
} catch (err) {
// won't run
}Because the throw happens later, outside the current sync call stack.
Fix:
- use
.catch() - or
awaitinsidetry/catch
4) Create custom errors (this is interview gold)
Custom error classes make debugging and UI decisions easier.
class HttpError extends Error {
constructor(message, { status, url, cause } = {}) {
super(message, { cause });
this.name = 'HttpError';
this.status = status;
this.url = url;
}
}
async function fetchJson(url) {
const res = await fetch(url);
if (!res.ok) {
throw new HttpError('Request failed', { status: res.status, url });
}
return res.json();
}Now the caller can do:
try {
await fetchJson('/api/user');
} catch (err) {
if (err instanceof HttpError && err.status === 401) {
// show login UI
}
}5) cause (modern JS)
Modern Error supports cause:
try {
doThing();
} catch (err) {
throw new Error('Failed to doThing', { cause: err });
}This helps preserve the original failure while adding context.
6) Pattern: convert unknown errors into a safe shape
In real apps, catch (err) gives unknown in TypeScript.
You should normalize it.
function toErrorMessage(err: unknown) {
if (err instanceof Error) return err.message;
return String(err);
}7) Frontend patterns: user-friendly errors
A) Show retryable states
- network errors → “Retry”
- empty states → “No results”
B) Log errors, but don’t expose secrets
- avoid logging tokens, PII
- redact request headers
C) Errors as part of state
const [state, setState] = useState({ loading: true, data: null, error: null });
try {
const data = await load();
setState({ loading: false, data, error: null });
} catch (err) {
setState({ loading: false, data: null, error: toErrorMessage(err) });
}8) Interview Q&A
Q: What’s the difference between throwing and rejecting?
- Throwing in async function becomes a rejected promise.
Q: How do you handle errors from fetch?
fetchonly rejects on network errors. For HTTP errors, checkres.okand throw.
Q: When do you use custom errors?
- When different failure types need different UI/retry behavior.
Summary checklist
- I can explain sync vs async error handling.
- I can explain
fetchHTTP errors vs network errors. - I know when to create custom errors.
- I separate user-friendly messages from debug details.
Summary
try/catchcatches sync errors and awaited async errors.- Use
.catchfor promise chains. - Create custom errors for better control and debugging.
- Normalize unknown errors for safe UI.
- Always design user-friendly fallback states.
Looking for structured, offline interview prep?
Free online lessons • Portable Amazon Kindle / Paperback
JavaScript Interview Guide
By Rajesh Dhiman
Deep dive into the Event Loop, microtask draining, closures, prototypal inheritance, and asynchronous execution patterns.
Frontend Interview Guide (2025 Edition)
By Rajesh Dhiman
Comprehensive preparation covering React internals, Web Performance, CSS architecture, and system design questions.
Note: All lessons on Frontend Interview remain completely free to read online. As an Amazon Associate, we may earn from qualifying purchases.
Written by Rajesh Dhiman
Authored by Rajesh Dhiman and engineered at Eunix Tech. Focused on deep-dive frontend engineering, JavaScript runtime internals, React architecture, and web performance.