Errors are inevitable in programming—invalid user input, network failures, unexpected data, or simple bugs. Without proper error handling, these errors crash your application and create poor user experiences. JavaScript's try-catch-finally statement provides a powerful mechanism to catch errors, handle them gracefully, and ensure cleanup code always runs. Mastering error handling transforms fragile code into robust, production- ready applications. Let's learn how to handle errors like a pro!
Why Error Handling Matters
// Without error handling - CRASHES
function divide(a, b) {
let result = a / b;
return result.toFixed(2);
}
console.log(divide(10, 2)); // "5.00" ✓
console.log(divide(10, 0)); // "Infinity" - unexpected!
let obj = null;
console.log(obj.name); // TypeError: Cannot read property 'name' of null
// Program CRASHES - everything stops!
console.log('This never runs');
// With error handling - RECOVERS
function safeDivide(a, b) {
try {
if (b === 0) {
throw new Error('Cannot divide by zero');
}
let result = a / b;
return result.toFixed(2);
} catch (error) {
console.log('Error:', error.message);
return 'Invalid';
}
}
console.log(safeDivide(10, 2)); // "5.00" ✓
console.log(safeDivide(10, 0)); // "Invalid" ✓
console.log('Program continues'); // ✓ Runs!Benefits of Error Handling:
- Prevents application crashes
- Provides better user experience
- Enables graceful error recovery
- Improves debugging with error information
- Ensures cleanup operations always run
Try-Catch Blocks
Basic Syntax
// Basic try-catch structure
try {
// Code that might throw an error
let result = riskyOperation();
console.log('Success:', result);
} catch (error) {
// Code to handle the error
console.log('Error occurred:', error.message);
}
// Simple example
try {
let data = JSON.parse('invalid json'); // Error!
console.log(data);
} catch (error) {
console.log('Failed to parse JSON');
}
// Output: Failed to parse JSON
// Program continues after catchHow Try-Catch Works
try {
console.log('1. Try block starts');
let obj = null;
console.log(obj.name); // Error occurs here!
console.log('2. This never runs'); // Skipped
} catch (error) {
console.log('3. Catch block executes');
console.log('Error:', error.message);
}
console.log('4. Program continues');
// Output:
// 1. Try block starts
// 3. Catch block executes
// Error: Cannot read property 'name' of null
// 4. Program continues
// When error occurs:
// 1. Try block stops immediately
// 2. Jumps to catch block
// 3. After catch, program continues normallyMultiple Operations in Try
function processUserData(jsonString) {
try {
// Step 1: Parse JSON
let data = JSON.parse(jsonString);
console.log('✓ JSON parsed');
// Step 2: Validate data
if (!data.name) {
throw new Error('Name is required');
}
console.log('✓ Data validated');
// Step 3: Process data
let processed = data.name.toUpperCase();
console.log('✓ Data processed');
return processed;
} catch (error) {
console.log('✗ Error at some step:', error.message);
return null;
}
}
// Test cases
processUserData('{"name": "Alice"}'); // Success
processUserData('invalid json'); // Parse error
processUserData('{"age": 25}'); // Validation errorThe Error Object
try {
let obj = null;
obj.name; // Error!
} catch (error) {
// Error object properties:
console.log('Name:', error.name); // "TypeError"
console.log('Message:', error.message); // "Cannot read property..."
console.log('Stack:', error.stack); // Stack trace
// Full error object
console.log(error);
// TypeError: Cannot read property 'name' of null
// at <anonymous>:2:7
// at ...
}
// Common error properties
try {
throw new Error('Something went wrong');
} catch (error) {
console.log(error.name); // "Error"
console.log(error.message); // "Something went wrong"
console.log(error.stack); // Full stack trace
}Built-in Error Types
// Different error types
// 1. ReferenceError - Variable doesn't exist
try {
console.log(nonExistentVariable);
} catch (error) {
console.log(error.name); // "ReferenceError"
}
// 2. TypeError - Wrong type operation
try {
let num = 5;
num.toUpperCase(); // Numbers don't have toUpperCase
} catch (error) {
console.log(error.name); // "TypeError"
}
// 3. SyntaxError - Invalid syntax (caught at parse time)
try {
eval('let x = '); // Invalid syntax
} catch (error) {
console.log(error.name); // "SyntaxError"
}
// 4. RangeError - Number out of range
try {
let arr = new Array(-1); // Negative length
} catch (error) {
console.log(error.name); // "RangeError"
}
// 5. URIError - Invalid URI functions
try {
decodeURIComponent('%'); // Invalid URI
} catch (error) {
console.log(error.name); // "URIError"
}The Finally Block
The finally block ALWAYS executes, whether an error occurs or not. Perfect for cleanup operations.
// Finally always runs
try {
console.log('Try block');
// Some code
} catch (error) {
console.log('Catch block');
} finally {
console.log('Finally block - ALWAYS runs');
}
// Output (no error):
// Try block
// Finally block - ALWAYS runs
// With error:
try {
console.log('Try block');
throw new Error('Oops!');
console.log('This never runs');
} catch (error) {
console.log('Catch block');
} finally {
console.log('Finally block - still runs!');
}
// Output:
// Try block
// Catch block
// Finally block - still runs!Finally for Cleanup
// Practical use: Cleanup operations
function readFile(filename) {
let file = openFile(filename); // Open resource
try {
let content = file.read();
return processContent(content);
} catch (error) {
console.log('Error reading file:', error.message);
return null;
} finally {
file.close(); // ALWAYS close file, even if error
console.log('File closed');
}
}
// Loading indicator example
function fetchData() {
showLoadingSpinner(); // Show loading
try {
let data = fetch('/api/data');
return data;
} catch (error) {
showError(error.message);
return null;
} finally {
hideLoadingSpinner(); // ALWAYS hide loading
}
}
// Database connection example
async function queryDatabase(query) {
let connection = await connectToDatabase();
try {
let result = await connection.query(query);
return result;
} catch (error) {
console.log('Query failed:', error.message);
throw error;
} finally {
await connection.close(); // ALWAYS close connection
}
}Finally with Return
// Finally runs even with return
function test() {
try {
console.log('Try');
return 'try return';
} catch (error) {
console.log('Catch');
return 'catch return';
} finally {
console.log('Finally');
// Finally runs before function returns!
}
}
console.log(test());
// Output:
// Try
// Finally
// try return
// Finally return overrides try/catch return!
function override() {
try {
return 'try';
} finally {
return 'finally'; // This wins!
}
}
console.log(override()); // "finally"
// Warning: This is confusing, avoid returning in finallyNested Try-Catch
// Nested try-catch for granular error handling
function processData(data) {
try {
console.log('Processing data...');
// Inner try-catch for specific operation
let parsed;
try {
parsed = JSON.parse(data);
} catch (parseError) {
console.log('JSON parse failed, using default');
parsed = { default: true };
}
// Continue with parsed data
console.log('Using data:', parsed);
// Another operation that might fail
let result = complexOperation(parsed);
return result;
} catch (error) {
console.log('Overall processing failed:', error.message);
return null;
}
}
// Multiple error sources
function loadUserProfile(userId) {
try {
let user = fetchUser(userId);
// Try to load avatar (optional)
try {
user.avatar = loadAvatar(user.avatarUrl);
} catch (avatarError) {
console.log('Avatar load failed, using default');
user.avatar = defaultAvatar;
}
// Try to load preferences (optional)
try {
user.preferences = loadPreferences(userId);
} catch (prefError) {
console.log('Preferences load failed, using defaults');
user.preferences = defaultPreferences;
}
return user;
} catch (error) {
console.log('Failed to load user:', error.message);
throw error;
}
}Best Practices
1. Catch Specific Errors
// Good: Check error type
try {
riskyOperation();
} catch (error) {
if (error instanceof TypeError) {
console.log('Type error:', error.message);
} else if (error instanceof ReferenceError) {
console.log('Reference error:', error.message);
} else {
console.log('Unknown error:', error.message);
}
}
// Better: Specific error handling
try {
let data = JSON.parse(userInput);
} catch (error) {
if (error.name === 'SyntaxError') {
showUserError('Invalid JSON format');
} else {
showUserError('An error occurred');
}
}2. Don't Swallow Errors
// Bad: Silent failure
try {
importantOperation();
} catch (error) {
// Empty catch - error disappears!
}
// Bad: Generic message
try {
importantOperation();
} catch (error) {
console.log('Error'); // No useful information
}
// Good: Log error details
try {
importantOperation();
} catch (error) {
console.error('Operation failed:', error.message);
console.error('Stack:', error.stack);
}
// Better: Log and handle
try {
importantOperation();
} catch (error) {
logError(error); // Log for debugging
showUserError('Operation failed. Please try again.');
notifyAdmin(error); // Alert team
}3. Use Finally for Cleanup
// Good pattern: Always cleanup
function processFile(filename) {
let file = null;
try {
file = openFile(filename);
let content = file.read();
return processContent(content);
} catch (error) {
console.log('Error:', error.message);
return null;
} finally {
if (file) {
file.close(); // Always close
}
}
}
// API request pattern
async function makeRequest(url) {
showLoading();
try {
let response = await fetch(url);
let data = await response.json();
displayData(data);
} catch (error) {
showError(error.message);
} finally {
hideLoading(); // Always hide loading
}
}4. Re-throw When Appropriate
// Sometimes you should re-throw
function processData(data) {
try {
return heavyProcessing(data);
} catch (error) {
console.log('Processing error:', error.message);
// Log it, but let caller handle it
throw error; // Re-throw
}
}
// Use it
try {
let result = processData(myData);
console.log('Success:', result);
} catch (error) {
// Caller can decide how to handle
notifyUser('Processing failed');
}Practical Examples
Example 1: Form Validation
function validateAndSubmitForm(formData) {
try {
// Validate required fields
if (!formData.email) {
throw new Error('Email is required');
}
if (!formData.email.includes('@')) {
throw new Error('Invalid email format');
}
if (!formData.password) {
throw new Error('Password is required');
}
if (formData.password.length < 8) {
throw new Error('Password must be at least 8 characters');
}
// If all validations pass
console.log('✓ Validation passed');
submitToServer(formData);
showSuccess('Form submitted successfully!');
} catch (error) {
console.log('✗ Validation failed:', error.message);
showError(error.message);
}
}
// Usage
let formData = {
email: 'user@example.com',
password: 'short'
};
validateAndSubmitForm(formData);
// Output: Password must be at least 8 charactersExample 2: API Request Handler
async function fetchUserData(userId) {
try {
console.log('Fetching user data...');
// Make API request
let response = await fetch(`/api/users/${userId}`);
// Check response status
if (!response.ok) {
throw new Error(`HTTP ${response.status}: ${response.statusText}`);
}
// Parse JSON
let data = await response.json();
// Validate data structure
if (!data.id || !data.name) {
throw new Error('Invalid data structure');
}
console.log('✓ User data loaded:', data.name);
return data;
} catch (error) {
console.error('✗ Failed to fetch user:', error.message);
// Different handling based on error type
if (error.message.includes('HTTP 404')) {
showError('User not found');
} else if (error.message.includes('HTTP 500')) {
showError('Server error. Please try again later.');
} else if (error.message === 'Failed to fetch') {
showError('Network error. Check your connection.');
} else {
showError('An error occurred. Please try again.');
}
return null;
} finally {
hideLoadingSpinner();
}
}
// Usage
let user = await fetchUserData(123);
if (user) {
displayUser(user);
}Example 3: Safe Calculator
function calculate(operation, a, b) {
try {
// Validate inputs
if (typeof a !== 'number' || typeof b !== 'number') {
throw new TypeError('Both operands must be numbers');
}
if (isNaN(a) || isNaN(b)) {
throw new Error('Invalid number');
}
let result;
switch (operation) {
case '+':
result = a + b;
break;
case '-':
result = a - b;
break;
case '*':
result = a * b;
break;
case '/':
if (b === 0) {
throw new Error('Division by zero');
}
result = a / b;
break;
default:
throw new Error(`Unknown operation: ${operation}`);
}
return result;
} catch (error) {
console.log('Calculation error:', error.message);
if (error instanceof TypeError) {
console.log('Please provide valid numbers');
}
return null;
}
}
// Test cases
console.log(calculate('+', 5, 3)); // 8
console.log(calculate('/', 10, 2)); // 5
console.log(calculate('/', 10, 0)); // null (division by zero)
console.log(calculate('*', '5', 3)); // null (type error)Try-Catch-Finally Mastery
Master JavaScript error handling
console.log() to see your output in the console above.Key Takeaways
- Try-catch prevents crashes by catching runtime errors
- Try block contains code that might throw errors
- Catch block handles errors when they occur
- Finally block ALWAYS runs (perfect for cleanup)
- Error object has name, message, and stack properties
- Common error types: TypeError, ReferenceError, SyntaxError, RangeError
- Always log error details for debugging
- Don't swallow errors—handle them properly
- Use finally for cleanup (close files, connections, hide loaders)
- Re-throw errors when caller should handle them
What's Next?
You now understand try-catch-finally blocks and can handle errors gracefully in your JavaScript applications! You've learned to catch errors, access error information, use finally for cleanup, and follow best practices for robust error handling.
In the next lesson, we'll explore Throwing Custom Errors—learning to create your own error types, throw errors intentionally, and build sophisticated error handling systems for complex applications.
💪 Practice Challenge:
Before moving on, try:
- Wrap risky operations in try-catch blocks
- Use finally to ensure cleanup code always runs
- Access and log error.name, error.message, and error.stack
- Build a form validator with proper error handling
- Handle different error types with specific messages