Docs
4.6-Try-Catch
4.6 Error Handling with try-catch
Overview
Error handling is a critical aspect of robust JavaScript programming. The try...catch statement allows you to handle runtime errors gracefully, preventing your application from crashing and providing meaningful feedback to users.
Table of Contents
- ā¢What Are Errors?
- ā¢The try...catch Statement
- ā¢The finally Block
- ā¢Error Object
- ā¢Throwing Errors
- ā¢Error Types
- ā¢Custom Errors
- ā¢Nested try-catch
- ā¢Error Handling Patterns
- ā¢Best Practices
What Are Errors?
Errors in JavaScript can occur for many reasons:
| Category | Examples |
|---|---|
| Syntax Errors | Typos, missing brackets, invalid code |
| Reference Errors | Using undefined variables |
| Type Errors | Calling non-functions, accessing null properties |
| Range Errors | Invalid array length, recursion limit |
| Runtime Errors | Network failures, file not found |
| Custom Errors | Business logic violations |
āāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāā
ā Error Categories ā
āāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāā¤
ā ā
ā Compile-Time Errors Runtime Errors ā
ā āāāāāāāāāāāāāāāāāā āāāāāāāāāāāāāā ā
ā ⢠Syntax errors ⢠Type errors ā
ā ⢠Cannot be caught ⢠Reference errors ā
ā ⢠Code won't run ⢠Range errors ā
ā ⢠Custom errors ā
ā ⢠CAN be caught ā
ā ā
āāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāā
The try...catch Statement
Basic Syntax
try {
// Code that might throw an error
riskyOperation();
} catch (error) {
// Handle the error
console.log('An error occurred:', error.message);
}
How It Works
āāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāā
ā try...catch Flow ā
āāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāā¤
ā ā
ā āāāāāāāāāāā ā
ā ā try ā ā
ā ā block ā ā
ā āāāāāā¬āāāāā ā
ā ā ā
ā āāāāā No Error āāāāāŗ Continue after try-catch ā
ā ā ā
ā āāāāā Error āāāāāŗ āāāāāāāāāāā ā
ā ā catch ā ā
ā ā block ā ā
ā āāāāāā¬āāāāā ā
ā ā ā
ā ā¼ ā
ā Continue execution ā
ā ā
āāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāā
Example
try {
const result = JSON.parse('invalid json');
console.log(result);
} catch (error) {
console.log('Failed to parse JSON:', error.message);
}
console.log('Program continues...');
The finally Block
The finally block executes regardless of whether an error occurred.
Syntax
try {
// Attempt operation
} catch (error) {
// Handle error
} finally {
// Always executes
// Cleanup code goes here
}
Use Cases
// Resource cleanup
let file;
try {
file = openFile('data.txt');
processFile(file);
} catch (error) {
console.error('Processing failed:', error);
} finally {
if (file) {
file.close(); // Always close the file
}
}
finally with Return Statements
function test() {
try {
return 'try';
} finally {
console.log('finally runs'); // This executes!
}
}
console.log(test());
// "finally runs"
// "try"
try...finally (without catch)
function riskyOperation() {
try {
// Do something that might fail
return performTask();
} finally {
// Cleanup regardless of success/failure
cleanup();
}
}
Error Object
When an error is caught, the catch block receives an Error object.
Error Properties
| Property | Description |
|---|---|
name | Type of error (e.g., "TypeError") |
message | Human-readable error description |
stack | Stack trace (non-standard but widely supported) |
Accessing Error Information
try {
undefined.property;
} catch (error) {
console.log('Name:', error.name); // "TypeError"
console.log('Message:', error.message); // "Cannot read property..."
console.log('Stack:', error.stack); // Full stack trace
}
Optional Catch Binding (ES2019+)
// If you don't need the error object
try {
riskyOperation();
} catch {
console.log('Something went wrong');
}
Throwing Errors
throw Statement
You can throw your own errors using the throw statement.
throw new Error('Something went wrong');
What Can Be Thrown?
// Preferred: Error objects
throw new Error('Error message');
throw new TypeError('Type error message');
throw new RangeError('Range error message');
// Possible but not recommended
throw 'Error string'; // Just a string
throw 42; // A number
throw { code: 500 }; // An object
throw true; // A boolean
Custom Validation
function divide(a, b) {
if (typeof a !== 'number' || typeof b !== 'number') {
throw new TypeError('Both arguments must be numbers');
}
if (b === 0) {
throw new Error('Cannot divide by zero');
}
return a / b;
}
try {
console.log(divide(10, 2)); // 5
console.log(divide(10, 0)); // Throws Error
} catch (error) {
console.error(error.message);
}
Error Types
JavaScript has several built-in error types:
| Type | Cause |
|---|---|
Error | Generic error (base class) |
SyntaxError | Invalid JavaScript syntax |
ReferenceError | Reference to undefined variable |
TypeError | Value is not expected type |
RangeError | Number outside allowable range |
URIError | Invalid URI encoding/decoding |
EvalError | Error in eval() (rarely used) |
AggregateError | Multiple errors wrapped together |
Examples
// ReferenceError
undefinedVariable; // ReferenceError
// TypeError
null.property; // TypeError
const x = 5;
x(); // TypeError: x is not a function
// RangeError
new Array(-1); // RangeError: Invalid array length
(1).toFixed(200); // RangeError
// SyntaxError (usually at parse time, can't be caught)
// JSON.parse can throw catchable SyntaxError
JSON.parse('{invalid}'); // SyntaxError
// URIError
decodeURIComponent('%'); // URIError
Checking 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('Other error:', error.message);
}
}
Custom Errors
Creating Custom Error Classes
class ValidationError extends Error {
constructor(message) {
super(message);
this.name = 'ValidationError';
}
}
class DatabaseError extends Error {
constructor(message, query) {
super(message);
this.name = 'DatabaseError';
this.query = query;
}
}
// Usage
function validateEmail(email) {
if (!email.includes('@')) {
throw new ValidationError('Invalid email format');
}
}
try {
validateEmail('invalid-email');
} catch (error) {
if (error instanceof ValidationError) {
console.log('Validation failed:', error.message);
} else {
throw error; // Re-throw unexpected errors
}
}
Error Hierarchy
class AppError extends Error {
constructor(message, code) {
super(message);
this.name = 'AppError';
this.code = code;
}
}
class NetworkError extends AppError {
constructor(message, statusCode) {
super(message, 'NETWORK_ERROR');
this.name = 'NetworkError';
this.statusCode = statusCode;
}
}
class AuthenticationError extends AppError {
constructor(message) {
super(message, 'AUTH_ERROR');
this.name = 'AuthenticationError';
}
}
Nested try-catch
Basic Nesting
try {
try {
throw new Error('Inner error');
} catch (innerError) {
console.log('Inner catch:', innerError.message);
throw new Error('Outer error'); // Re-throw
}
} catch (outerError) {
console.log('Outer catch:', outerError.message);
}
Re-throwing Errors
function processData(data) {
try {
return JSON.parse(data);
} catch (error) {
// Log for debugging
console.error('Parse error:', error);
// Re-throw with more context
throw new Error(`Failed to process data: ${error.message}`);
}
}
Error Handling Patterns
1. Guard Clause Pattern
function processUser(user) {
if (!user) {
throw new Error('User is required');
}
if (!user.email) {
throw new Error('User email is required');
}
// Main logic after validation
return sendEmail(user.email);
}
2. Fail-Fast Pattern
function initialize(config) {
if (!config.apiKey) {
throw new Error('API key is required');
}
if (!config.endpoint) {
throw new Error('Endpoint is required');
}
// Continue initialization...
}
3. Error Wrapper Pattern
function wrapError(fn, errorMessage) {
return function (...args) {
try {
return fn(...args);
} catch (error) {
throw new Error(`${errorMessage}: ${error.message}`);
}
};
}
const safeParseJSON = wrapError(JSON.parse, 'JSON parsing failed');
4. Default Value Pattern
function safeParse(json, defaultValue = null) {
try {
return JSON.parse(json);
} catch {
return defaultValue;
}
}
console.log(safeParse('{"valid": true}')); // { valid: true }
console.log(safeParse('invalid')); // null
5. Error Aggregation
function validateForm(data) {
const errors = [];
if (!data.name) {
errors.push('Name is required');
}
if (!data.email) {
errors.push('Email is required');
}
if (data.age < 0) {
errors.push('Age must be positive');
}
if (errors.length > 0) {
throw new AggregateError(errors, 'Validation failed');
}
return true;
}
6. Async Error Handling (Preview)
async function fetchData(url) {
try {
const response = await fetch(url);
if (!response.ok) {
throw new Error(`HTTP error: ${response.status}`);
}
return await response.json();
} catch (error) {
console.error('Fetch failed:', error.message);
throw error;
}
}
Best Practices
1. Be Specific About What You Catch
// ā Too broad
try {
// Many operations
const data = fetchData();
const parsed = parseData(data);
const result = processData(parsed);
} catch (error) {
console.log('Something failed');
}
// ā
Specific error handling
try {
const data = fetchData();
try {
const parsed = parseData(data);
} catch (parseError) {
throw new Error(`Parse failed: ${parseError.message}`);
}
const result = processData(parsed);
} catch (error) {
console.log('Specific error:', error.message);
}
2. Don't Swallow Errors
// ā Bad: Error is silently ignored
try {
riskyOperation();
} catch (error) {
// Doing nothing
}
// ā
Good: Error is logged or handled
try {
riskyOperation();
} catch (error) {
console.error('Operation failed:', error);
// Or: throw error; (re-throw)
// Or: return defaultValue;
}
3. Use Appropriate Error Types
// ā Generic error
throw new Error('Invalid input');
// ā
Specific error type
throw new TypeError('Expected a string');
throw new RangeError('Value must be between 1 and 100');
throw new ValidationError('Email format is invalid');
4. Include Contextual Information
// ā Not helpful
throw new Error('Failed');
// ā
Helpful error message
throw new Error(`Failed to save user ${userId}: ${originalError.message}`);
5. Always Clean Up Resources
let connection;
try {
connection = openDatabase();
// Do operations
} catch (error) {
console.error('Database error:', error);
} finally {
if (connection) {
connection.close(); // Always cleanup
}
}
6. Validate Early
function createUser(name, age) {
// Validate at the start
if (typeof name !== 'string') {
throw new TypeError('Name must be a string');
}
if (typeof age !== 'number' || age < 0) {
throw new TypeError('Age must be a positive number');
}
// Main logic after validation
return { name, age, createdAt: new Date() };
}
Summary
āāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāā
ā Error Handling Quick Reference ā
āāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāā¤
ā ā
ā try { ā
ā // Code that might throw ā
ā } catch (error) { ā
ā // Handle error ā
ā // error.name, error.message, error.stack ā
ā } finally { ā
ā // Always runs - cleanup code ā
ā } ā
ā ā
ā Throwing: throw new Error("message"); ā
ā ā
ā Built-in Types: ā
ā ⢠Error, TypeError, ReferenceError ā
ā ⢠RangeError, SyntaxError, URIError ā
ā ā
ā Best Practices: ā
ā ⢠Be specific about errors ā
ā ⢠Don't swallow errors silently ā
ā ⢠Include helpful messages ā
ā ⢠Always clean up in finally ā
ā ⢠Validate inputs early ā
ā ā
āāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāā
Next Steps
- ā¢Practice with the examples in
examples.js - ā¢Complete the exercises in
exercises.js - ā¢Learn about async error handling with Promises
- ā¢Explore logging and monitoring strategies