🐛 Error Handling and Debugging

Managing and Fixing Code Issues

Understanding Errors

Errors are inevitable in programming. Proper error handling makes code more robust and easier to debug.

🚨 Types of Errors

SyntaxError

// Missing closing parenthesis
// console.log('Hello';  // SyntaxError

// Invalid syntax
// let 123name = 'John';  // SyntaxError

// Missing comma
// let obj = { a: 1 b: 2 };  // SyntaxError

// Caught at parse time (before execution)
// Cannot be caught with try-catch

ReferenceError

// Using undeclared variable
console.log(nonExistent);  // ReferenceError: nonExistent is not defined

// Accessing before declaration (TDZ)
console.log(x);  // ReferenceError
let x = 5;

// Misspelled variable
let userName = 'John';
console.log(username);  // ReferenceError (case sensitive)

TypeError

// Calling non-function
let num = 42;
num();  // TypeError: num is not a function

// Accessing property of undefined/null
let obj = null;
console.log(obj.name);  // TypeError: Cannot read property 'name' of null

// Reassigning const
const x = 5;
x = 10;  // TypeError: Assignment to constant variable

// Wrong argument type
let str = 'hello';
str.toFixed(2);  // TypeError: str.toFixed is not a function

RangeError

// Array length out of range
let arr = new Array(-1);  // RangeError: Invalid array length

// Number out of range
let num = (999).toFixed(200);  // RangeError: toFixed() digits argument must be between 0 and 100

// Maximum call stack (infinite recursion)
function infinite() {
    infinite();
}
infinite();  // RangeError: Maximum call stack size exceeded

Other Error Types

// URIError - invalid URI functions
decodeURIComponent('%');  // URIError

// EvalError - eval() errors (rarely used)

// Custom errors (covered later)
throw new Error('Custom error message');

🛡️ try...catch...finally

Basic try-catch

// Catch and handle errors
try {
    let result = riskyOperation();
    console.log(result);
} catch (error) {
    console.error('Error occurred:', error.message);
}

// Example: parsing JSON
try {
    let data = JSON.parse('invalid json');
} catch (error) {
    console.error('Failed to parse:', error);
    // Provide fallback
    let data = { default: true };
}

// Code continues after catch
console.log('Program continues');

Error Object Properties

try {
    nonExistent.property;
} catch (error) {
    console.log(error.name);      // 'ReferenceError'
    console.log(error.message);   // 'nonExistent is not defined'
    console.log(error.stack);     // Stack trace string
    console.log(error.toString()); // 'ReferenceError: nonExistent is not defined'
}

// Conditional error handling
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);
    }
}

finally Block

// finally always runs (success or error)
try {
    console.log('Trying...');
    throw new Error('Oops');
} catch (error) {
    console.log('Caught:', error.message);
} finally {
    console.log('Finally runs always');
}

// Practical: cleanup
let file;
try {
    file = openFile('data.txt');
    processFile(file);
} catch (error) {
    console.error('Error processing file:', error);
} finally {
    if (file) {
        closeFile(file);  // Always close file
    }
}

// finally runs even with return
function test() {
    try {
        return 'try';
    } catch (error) {
        return 'catch';
    } finally {
        console.log('Finally runs');
        // Note: return in finally overrides try/catch return
    }
}

test();  // Logs "Finally runs", returns "try"

Nested try-catch

try {
    try {
        throw new Error('Inner error');
    } catch (error) {
        console.log('Inner catch:', error.message);
        throw new Error('Outer error');  // Re-throw or throw new
    }
} catch (error) {
    console.log('Outer catch:', error.message);
}

// Practical: try operations in sequence
function processData(data) {
    try {
        let parsed = JSON.parse(data);
        
        try {
            validate(parsed);
            return transform(parsed);
        } catch (error) {
            console.error('Validation error:', error);
            return getDefaultData();
        }
    } catch (error) {
        console.error('Parse error:', error);
        return null;
    }
}

🎯 Throwing Errors

throw Statement

// Throw string (not recommended)
throw 'Error message';

// Throw number
throw 404;

// Throw object
throw { code: 404, message: 'Not found' };

// Throw Error (best practice)
throw new Error('Something went wrong');

// Different error types
throw new TypeError('Invalid type');
throw new ReferenceError('Variable not found');
throw new RangeError('Out of range');

Custom Errors

// ES6 class extending Error
class ValidationError extends Error {
    constructor(message, field) {
        super(message);
        this.name = 'ValidationError';
        this.field = field;
    }
}

// Usage
function validateEmail(email) {
    if (!email.includes('@')) {
        throw new ValidationError('Invalid email format', 'email');
    }
}

try {
    validateEmail('notanemail');
} catch (error) {
    if (error instanceof ValidationError) {
        console.log(`Validation failed for ${error.field}: ${error.message}`);
    } else {
        console.log('Other error:', error);
    }
}

// More custom errors
class NotFoundError extends Error {
    constructor(resource) {
        super(`${resource} not found`);
        this.name = 'NotFoundError';
        this.statusCode = 404;
    }
}

class AuthenticationError extends Error {
    constructor(message = 'Authentication failed') {
        super(message);
        this.name = 'AuthenticationError';
        this.statusCode = 401;
    }
}

// Using custom errors
function findUser(id) {
    let user = database.find(id);
    if (!user) {
        throw new NotFoundError('User');
    }
    return user;
}

function requireAuth(user) {
    if (!user.isAuthenticated) {
        throw new AuthenticationError();
    }
}

When to Throw Errors

// Validate function arguments
function divide(a, b) {
    if (typeof a !== 'number' || typeof b !== 'number') {
        throw new TypeError('Arguments must be numbers');
    }
    if (b === 0) {
        throw new RangeError('Cannot divide by zero');
    }
    return a / b;
}

// Check preconditions
function processUser(user) {
    if (!user) {
        throw new Error('User is required');
    }
    if (!user.id) {
        throw new ValidationError('User ID is required', 'id');
    }
    // Process user
}

// Indicate impossible states
function handleStatus(status) {
    switch (status) {
        case 'pending':
            return 'Processing...';
        case 'complete':
            return 'Done';
        case 'error':
            return 'Failed';
        default:
            throw new Error(`Unknown status: ${status}`);
    }
}

🔍 Debugging Techniques

console Methods

// Basic logging
console.log('Message', variable);

// Multiple values
console.log('User:', user, 'Age:', age);

// Formatted strings
console.log('User %s is %d years old', name, age);

// Error level (shows red in console)
console.error('Error occurred:', error);

// Warning level (shows yellow)
console.warn('Deprecated function used');

// Info level
console.info('Application started');

// Debug level (may be filtered)
console.debug('Debug data:', data);

// Table format (great for arrays/objects)
let users = [
    { id: 1, name: 'John', age: 30 },
    { id: 2, name: 'Jane', age: 25 }
];
console.table(users);

// Group related logs
console.group('User Details');
console.log('Name:', user.name);
console.log('Age:', user.age);
console.groupEnd();

// Collapsed group
console.groupCollapsed('Advanced Info');
console.log('Detail 1');
console.log('Detail 2');
console.groupEnd();

// Time operations
console.time('Operation');
expensiveOperation();
console.timeEnd('Operation');  // Logs elapsed time

// Count occurrences
function onClick() {
    console.count('Clicked');  // Clicked: 1, Clicked: 2, etc.
}

// Assert (logs only if false)
console.assert(user.age >= 18, 'User must be adult');

// Stack trace
console.trace('Call stack');

Debugger Statement

function complexFunction(data) {
    let processed = preProcess(data);
    
    debugger;  // Execution pauses here if DevTools open
    
    let result = mainProcess(processed);
    return result;
}

// Conditional debugging
function debug(value, threshold) {
    if (value > threshold) {
        debugger;  // Only pause if condition met
    }
}

Error Boundaries Pattern

// Wrap risky code
function safeExecute(fn, fallback) {
    try {
        return fn();
    } catch (error) {
        console.error('Error in safe execute:', error);
        return fallback !== undefined ? fallback : null;
    }
}

// Usage
let result = safeExecute(() => {
    return riskyOperation();
}, 'default value');

// Multiple operations
function executeAll(...functions) {
    let results = [];
    let errors = [];
    
    functions.forEach((fn, index) => {
        try {
            results.push(fn());
        } catch (error) {
            errors.push({ index, error });
            results.push(null);
        }
    });
    
    if (errors.length > 0) {
        console.error('Some operations failed:', errors);
    }
    
    return results;
}

// Usage
let [result1, result2, result3] = executeAll(
    () => operation1(),
    () => operation2(),
    () => operation3()
);

🎨 Best Practices

// 1. Fail fast - validate early
function processOrder(order) {
    if (!order) throw new Error('Order required');
    if (!order.id) throw new Error('Order ID required');
    if (order.total < 0) throw new RangeError('Invalid total');
    
    // Process order...
}

// 2. Specific error messages
throw new Error('User not found');  // Good
throw new Error('Error');           // Bad

// 3. Use appropriate error types
throw new TypeError('Expected string');     // For type errors
throw new RangeError('Value out of range'); // For range errors

// 4. Don't swallow errors silently
try {
    riskyOperation();
} catch (error) {
    // Bad: empty catch
}

try {
    riskyOperation();
} catch (error) {
    // Good: at least log it
    console.error('Operation failed:', error);
}

// 5. Provide context
function loadConfig(filename) {
    try {
        return JSON.parse(fs.readFileSync(filename));
    } catch (error) {
        throw new Error(`Failed to load config from ${filename}: ${error.message}`);
    }
}

// 6. Don't use exceptions for control flow
// Bad
try {
    for (let i = 0; ; i++) {
        console.log(array[i]);
    }
} catch (error) {
    // End of array
}

// Good
for (let i = 0; i < array.length; i++) {
    console.log(array[i]);
}

// 7. Document thrown errors
/**
 * Divides two numbers
 * @param {number} a - Dividend
 * @param {number} b - Divisor
 * @returns {number} Result
 * @throws {TypeError} If arguments are not numbers
 * @throws {RangeError} If b is zero
 */
function divide(a, b) {
    if (typeof a !== 'number' || typeof b !== 'number') {
        throw new TypeError('Arguments must be numbers');
    }
    if (b === 0) {
        throw new RangeError('Cannot divide by zero');
    }
    return a / b;
}

💡 Practical Examples

Input Validation

function validateUserInput(data) {
    let errors = [];
    
    if (!data.email || !data.email.includes('@')) {
        errors.push('Invalid email');
    }
    
    if (!data.password || data.password.length < 8) {
        errors.push('Password must be 8+ characters');
    }
    
    if (!data.age || data.age < 18) {
        errors.push('Must be 18 or older');
    }
    
    if (errors.length > 0) {
        throw new ValidationError(errors.join(', '));
    }
    
    return true;
}

// Usage
try {
    validateUserInput(formData);
    submitForm(formData);
} catch (error) {
    if (error instanceof ValidationError) {
        displayErrors(error.message);
    } else {
        console.error('Unexpected error:', error);
    }
}

API Error Handling

async function fetchUser(id) {
    try {
        let response = await fetch(`/api/users/${id}`);
        
        if (!response.ok) {
            if (response.status === 404) {
                throw new NotFoundError('User');
            } else if (response.status === 401) {
                throw new AuthenticationError();
            } else {
                throw new Error(`HTTP ${response.status}: ${response.statusText}`);
            }
        }
        
        return await response.json();
    } catch (error) {
        if (error instanceof NotFoundError) {
            console.error('User not found');
            return null;
        } else if (error instanceof AuthenticationError) {
            console.error('Not authenticated');
            redirectToLogin();
        } else {
            console.error('Failed to fetch user:', error);
            throw error;  // Re-throw unexpected errors
        }
    }
}

Retry with Error Handling

async function retryOperation(fn, maxRetries = 3, delay = 1000) {
    for (let attempt = 1; attempt <= maxRetries; attempt++) {
        try {
            return await fn();
        } catch (error) {
            console.log(`Attempt ${attempt} failed:`, error.message);
            
            if (attempt === maxRetries) {
                throw new Error(`Failed after ${maxRetries} attempts: ${error.message}`);
            }
            
            // Wait before retry
            await new Promise(resolve => setTimeout(resolve, delay));
        }
    }
}

// Usage
try {
    let result = await retryOperation(
        () => fetch('/api/data').then(r => r.json()),
        3,
        1000
    );
    console.log('Success:', result);
} catch (error) {
    console.error('All retries failed:', error);
    showErrorMessage('Unable to load data');
}

Global Error Handler

// Catch unhandled errors
window.addEventListener('error', event => {
    console.error('Uncaught error:', event.error);
    
    // Log to error tracking service
    logError({
        message: event.error.message,
        stack: event.error.stack,
        url: window.location.href,
        userAgent: navigator.userAgent
    });
    
    // Show user-friendly message
    showNotification('An error occurred. Please try again.');
    
    // Prevent default browser error handling
    event.preventDefault();
});

// Catch unhandled promise rejections
window.addEventListener('unhandledrejection', event => {
    console.error('Unhandled promise rejection:', event.reason);
    
    logError({
        type: 'unhandledRejection',
        reason: event.reason
    });
    
    event.preventDefault();
});

Graceful Degradation

function initializeApp() {
    try {
        // Try to use modern feature
        if ('IntersectionObserver' in window) {
            initLazyLoading();
        } else {
            // Fallback for old browsers
            loadAllImages();
        }
    } catch (error) {
        console.error('Failed to initialize:', error);
        // Use basic functionality
        initBasicMode();
    }
    
    try {
        // Try to load user preferences
        let prefs = JSON.parse(localStorage.getItem('prefs'));
        applyPreferences(prefs);
    } catch (error) {
        console.warn('Could not load preferences:', error);
        // Use defaults
        applyPreferences(getDefaultPreferences());
    }
    
    try {
        // Try to enable analytics
        initAnalytics();
    } catch (error) {
        // Non-critical, just log
        console.warn('Analytics unavailable:', error);
    }
}

🎯 Key Takeaways