try/catch/finally
The try/catch/finally statement handles exceptions and cleanup operations.
Basic Syntax
try {
// Code that might throw an error
const result = riskyOperation();
} catch (error) {
// Handle the error
console.error('Error:', error.message);
} finally {
// Always runs, regardless of success or failure
cleanup();
}
Without catch or finally
// try with finally (no catch)
try {
doSomething();
} finally {
cleanup(); // Runs even if doSomething throws
}
// try with catch (no finally)
try {
doSomething();
} catch (error) {
handleError(error);
}
Error Object Properties
try {
riskyOperation();
} catch (error) {
console.log(error.name); // 'TypeError', 'ReferenceError', etc.
console.log(error.message); // Human-readable description
console.log(error.stack); // Stack trace
}
Nested try/catch
try {
try {
operation1();
} catch (error) {
console.log('Operation 1 failed:', error);
operation2();
}
} catch (error) {
console.log('Operation 2 failed:', error);
}
Custom Errors
You can create custom error types for better error handling and debugging.
Creating Custom Errors
class ValidationError extends Error {
constructor(message, field) {
super(message);
this.name = 'ValidationError';
this.field = field;
}
}
// Usage
function validateAge(age) {
if (age < 0 || age > 150) {
throw new ValidationError('Invalid age', 'age');
}
return age;
}
try {
validateAge(200);
} catch (error) {
if (error instanceof ValidationError) {
console.log(`${error.field}: ${error.message}`);
}
}
Common Custom Errors
// API Error
class ApiError extends Error {
constructor(message, statusCode, data) {
super(message);
this.name = 'ApiError';
this.statusCode = statusCode;
this.data = data;
}
}
// Not Found Error
class NotFoundError extends Error {
constructor(resource, id) {
super(`${resource} with id ${id} not found`);
this.name = 'NotFoundError';
this.resource = resource;
this.id = id;
}
}
// Authentication Error
class AuthError extends Error {
constructor(message = 'Authentication required') {
super(message);
this.name = 'AuthError';
}
}
Error Handling by Type
try {
// Some operation
} catch (error) {
if (error instanceof ValidationError) {
// Handle validation
} else if (error instanceof ApiError) {
// Handle API error
} else if (error instanceof TypeError) {
// Handle type error
} else {
// Handle unknown errors
console.error('Unexpected error:', error);
}
}
Error Propagation
Errors propagate up the call stack until caught. Understanding this helps write robust code.
How Errors Propagate
function outer() {
inner(); // Error from inner propagates here
}
function inner() {
middle(); // Error from middle propagates here
}
function middle() {
throw new Error('Something went wrong');
}
try {
outer(); // Caught here
} catch (error) {
console.error(error.message); // 'Something went wrong'
}
Re-throwing Errors
function processData(data) {
try {
validateData(data);
return transform(data);
} catch (error) {
console.log('Processing failed:', error.message);
throw error; // Re-throw for caller to handle
}
}
Error Handling in Async Code
// Promise chain
fetchData()
.then(process)
.catch(handleError); // Catches errors from any step
// Async/await
async function main() {
try {
const data = await fetchData();
const result = await process(data);
return result;
} catch (error) {
handleError(error);
}
}
Best Practices
// 1. Always provide context
throw new Error(`Failed to process user ${userId}: ${originalError.message}`);
// 2. Use specific error types
throw new ValidationError('Email is required', 'email');
// 3. Don't swallow errors silently
try {
riskyOperation();
} catch (error) {
console.error(error); // At minimum, log it
}
// 4. Clean up in finally
try {
const resource = acquireResource();
useResource(resource);
} finally {
releaseResource(); // Always cleanup
}
Practice Problems
Create a reusable React component implementing Error Handling. Include proper state management and accessibility.
Solution
// Production-ready component with:
// - Proper TypeScript types
// - Accessibility (ARIA)
// - Error boundaries
// - Loading states
// - Memoization where neededWrite unit and integration tests for Error Handling using React Testing Library.
Solution
// Test coverage:
// 1. Rendering tests
// 2. Interaction tests
// 3. Edge case tests
// 4. Accessibility testsOptimize Error Handling for performance. Consider memoization, code splitting, and bundle size.
Solution
// Optimization techniques:
// 1. React.memo / useMemo / useCallback
// 2. Code splitting with lazy()
// 3. Virtual scrolling for lists
// 4. Image lazy loading
// 5. Bundle analysisQuiz
1. When does the finally block execute?
2. What is the benefit of custom errors?
3. What happens if no catch block handles an error?
4. How do you re-throw an error?
Flashcards
Question
What is try/catch/finally?
Click to reveal answer
Answer
Statements for handling exceptions. try wraps risky code, catch handles errors, finally runs cleanup regardless of outcome.
Question
What is error propagation?
Click to reveal answer
Answer
Errors travel up the call stack until caught by a catch block. If uncaught, they become uncaught exceptions.
Question
How to create a custom error?
Click to reveal answer
Answer
Extend the Error class: class MyError extends Error { constructor(msg) { super(msg); this.name = 'MyError'; } }
Question
What does the error object contain?
Click to reveal answer
Answer
name (error type), message (description), and stack (stack trace showing where error occurred).
Question
What is Error Handling?
Click to reveal answer
Answer
Error Handling is a key concept in frontend development.
Revision Notes
Key Takeaways
- 1.try/catch/finally handles errors and cleanup
- 2.Errors propagate up the call stack until caught
- 3.Custom errors improve debugging and handling
- 4.finally blocks always execute, regardless of errors
- 5.Always provide context when throwing errors
Interview Tips
- •Explain when to use try/catch vs .catch()
- •Discuss error propagation with async code
- •Show how to create meaningful custom error types
- •Explain the importance of cleanup in finally blocks
Cheat Sheet
Error Handling Cheat Sheet
Basic Syntax
try {
riskyOperation();
} catch (error) {
handleError(error);
} finally {
cleanup();
}
Custom Errors
class MyError extends Error {
constructor(msg) {
super(msg);
this.name = 'MyError';
}
}
Error Object
name: Error typemessage: Descriptionstack: Stack trace
Best Practices
- Always provide context in error messages
- Use specific error types
- Clean up in finally blocks
- Don't swallow errors silently