try {
JSON.parse('not valid json');
} catch (err) {
console.log('Failed to parse:', err.message);
}
Topics
37
ArrayBuffer, TypedArrays & Binary Data
Arrays & Array Methods
Async Iterators & Streams
Browser Storage & Web APIs
Classes & Class Syntax
Date, Time & Internationalization (Intl API)
Debugging, Testing & Tooling
Design Patterns in JavaScript
Destructuring, Spread & Rest
DOM & Events
Error Handling
ES Modules
Event Loop & Concurrency
Functional Programming
Iterators & Generators
JSON & Data Serialization
Map, Set, WeakMap & WeakSet
Memory Management & Garbage Collection
Networking: Fetch, XHR, WebSockets & CORS
Numbers, Math & BigInt
Objects, Property Descriptors & Immutability
Optional Chaining & Nullish Coalescing
Package Management, Bundlers & Transpilation (npm, Webpack/Vite, Babel)
Performance Optimization: Debouncing, Throttling & Memoization
Promises & async/await
Prototypes & Inheritance
Proxy & Reflect
Regular Expressions
Scope, Hoisting & Closures
Security: XSS, CSRF & Content Security Policy
Service Workers & Progressive Web Apps
Strings & Template Literals
Symbols & Well-Known Symbols
this & Binding
Types & Coercion
Web Components & Custom Elements
Web Workers & Multithreading
Error Handling
10 questions found
Code inside try runs normally; if it throws an error, execution immediately jumps to the catch block instead of crashing the whole program, letting you handle the error gracefully.
Real-world example
Safely parsing user-provided JSON input without crashing the app on malformed data.
JSON & Data Serialization
finally always runs after try/catch, whether or not an error was thrown or caught, and even if the try block returns early — making it ideal for cleanup code that must always execute.
function readFile() {
openFile();
try {
return process();
} finally {
closeFile(); // always runs
}
}
Real-world example
Ensuring a loading spinner is hidden whether an API call succeeds or fails.
Async Iterators & Streams
new Error('message') creates an object with a stack trace and standard .message/.name properties, which DevTools and error-tracking tools understand. Throwing a plain string or object loses the stack trace, making debugging much harder.
throw new Error('Invalid input'); // has a stack trace
throw 'Invalid input'; // no stack trace, harder to debug
Real-world example
Always throwing proper Error instances (or subclasses) so production error-tracking tools like Sentry capture full context.
Classes & Class Syntax
Extend the built-in Error class, call super(message) in the constructor to set up the message and stack trace, then add any extra properties specific to your error type.
class ValidationError extends Error {
constructor(message, field) {
super(message);
this.name = 'ValidationError';
this.field = field;
}
}
throw new ValidationError('Required', 'email');
Real-world example
Distinguishing a ValidationError from a NetworkError in a catch block to show different UI messages.
Classes & Class Syntax
With .then()/.catch() chains, attach a .catch() at the end to handle rejections from any prior step. With async/await, wrap the awaited calls in a try/catch block — both approaches ultimately catch the same underlying rejected Promise.
// Promise chain
fetchData().then(process).catch(err => console.error(err));
// async/await
try {
const data = await fetchData();
process(data);
} catch (err) {
console.error(err);
}
Real-world example
Handling a failed API call gracefully instead of an unhandled promise rejection crashing the app.
Promises & async/await
If a function doesn't catch an error itself, it automatically propagates up the call stack to the nearest enclosing try/catch (or crashes the program/rejects the promise if none exists) — you don't need to manually re-throw at every level.
function inner() { throw new Error('boom'); }
function outer() { inner(); } // doesn't need its own try/catch
try {
outer();
} catch (err) {
console.log('Caught:', err.message); // 'Caught: boom'
}
Real-world example
Letting a deeply nested validation function throw, and catching it once at the top-level request handler.
Design Patterns in JavaScript
The { cause } option on the Error constructor lets you wrap a lower-level error inside a higher-level, more meaningful one while preserving the original error for debugging — avoiding the common trade-off of losing context vs. re-throwing raw low-level errors.
try {
await fetchUser(id);
} catch (err) {
throw new Error('Failed to load user profile', { cause: err });
}
Real-world example
Wrapping a low-level 'fetch failed' network error with a higher-level, user-facing 'Failed to load profile' error while keeping the original for logs.
Networking: Fetch
XHR
WebSockets & CORS
window.addEventListener('error', ...) catches uncaught synchronous exceptions bubbling up to the top; window.addEventListener('unhandledrejection', ...) catches Promise rejections that were never handled by a .catch(). Together they form a last-resort safety net for logging errors that slipped past local handling.
window.addEventListener('unhandledrejection', (event) => {
console.error('Unhandled rejection:', event.reason);
event.preventDefault(); // suppress default browser logging
});
Real-world example
Reporting any error that slips through the app's normal handling to a monitoring service like Sentry.
Promises & async/await
Why can errors thrown inside a setTimeout callback NOT be caught by a surrounding try/catch?
AdvancedThe try/catch block finishes executing (and its stack frame is gone) long before the setTimeout callback actually runs on a later turn of the event loop — by the time the error is thrown, there's no enclosing try/catch left to catch it.
try {
setTimeout(() => { throw new Error('too late!'); }, 100);
} catch (err) {
// never runs — the throw happens asynchronously, after this try/catch exits
}
Real-world example
A confusing bug where a developer expects a try/catch to catch an error from an async callback, but it silently escapes to the console instead.
Event Loop & Concurrency
Wrap the call in a loop with a try/catch; on failure, wait an increasing delay (e.g. exponential backoff) before retrying, up to a maximum attempt count, then re-throw the final error if all attempts fail.
async function retry(fn, attempts = 3, delay = 500) {
for (let i = 0; i < attempts; i++) {
try {
return await fn();
} catch (err) {
if (i === attempts - 1) throw err;
await new Promise(r => setTimeout(r, delay * 2 ** i));
}
}
}
Real-world example
Retrying a flaky network request up to three times with increasing delays before giving up and showing an error.
Promises & async/await