Skip to main content
โšก Calmops

Error Handling in JavaScript

Error Handling in JavaScript

Proper error handling makes your code robust and easier to debug. JavaScript provides several mechanisms for handling errors.

Try-Catch-Finally

The fundamental error handling structure:

try {
    // Code that might throw an error
    const result = riskyOperation();
} catch (error) {
    // Handle the error
    console.log("Error occurred:", error.message);
} finally {
    // Runs regardless of success or error
    console.log("Cleanup code");
}

Basic Example

try {
    const data = JSON.parse("invalid json");
} catch (error) {
    console.log("Failed to parse JSON:", error.message);
}

Accessing Error Information

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
}

Error Types

JavaScript has several built-in error types:

SyntaxError

Occurs when code has invalid syntax:

// This would cause a SyntaxError at parse time
// const x = ;

ReferenceError

Accessing undefined variables:

try {
    console.log(undefinedVariable);
} catch (error) {
    console.log(error instanceof ReferenceError); // true
}

TypeError

Wrong type or invalid operation:

try {
    const x = null;
    x.property; // TypeError: Cannot read property 'property' of null
} catch (error) {
    console.log(error instanceof TypeError); // true
}

RangeError

Value out of valid range:

try {
    const arr = new Array(-1); // RangeError
} catch (error) {
    console.log(error instanceof RangeError); // true
}

Throwing Errors

Throw Statement

function validateAge(age) {
    if (age < 0) {
        throw new Error("Age cannot be negative");
    }
    return age;
}

try {
    validateAge(-5);
} catch (error) {
    console.log(error.message); // "Age cannot be negative"
}

Throwing Different Types

function processData(data) {
    if (!data) {
        throw new TypeError("Data is required");
    }
    
    if (data.length === 0) {
        throw new RangeError("Data cannot be empty");
    }
    
    return data;
}

Custom Errors

Create custom error classes for specific situations:

class ValidationError extends Error {
    constructor(message) {
        super(message);
        this.name = "ValidationError";
    }
}

try {
    throw new ValidationError("Invalid email format");
} catch (error) {
    if (error instanceof ValidationError) {
        console.log("Validation failed:", error.message);
    }
}

More Complex Custom Error

class APIError extends Error {
    constructor(message, statusCode, response) {
        super(message);
        this.name = "APIError";
        this.statusCode = statusCode;
        this.response = response;
    }
}

try {
    throw new APIError("Not found", 404, { error: "Resource not found" });
} catch (error) {
    if (error instanceof APIError) {
        console.log(`API Error ${error.statusCode}: ${error.message}`);
    }
}

Finally Block

The finally block always executes:

function example() {
    try {
        console.log("Try block");
        return "from try";
    } catch (error) {
        console.log("Catch block");
        return "from catch";
    } finally {
        console.log("Finally block"); // Always runs
    }
}

console.log(example());
// Output:
// Try block
// Finally block
// from try

Cleanup with Finally

function readFile(filename) {
    let file;
    try {
        file = openFile(filename);
        return file.read();
    } catch (error) {
        console.log("Error reading file:", error.message);
    } finally {
        if (file) {
            file.close(); // Always close the file
        }
    }
}

Practical Examples

Parsing JSON Safely

function parseJSON(jsonString) {
    try {
        return JSON.parse(jsonString);
    } catch (error) {
        console.log("Invalid JSON:", error.message);
        return null;
    }
}

console.log(parseJSON('{"name": "Alice"}')); // { name: "Alice" }
console.log(parseJSON("invalid")); // null

API Call with Error Handling

async function fetchUser(userId) {
    try {
        const response = await fetch(`/api/users/${userId}`);
        
        if (!response.ok) {
            throw new Error(`HTTP error! status: ${response.status}`);
        }
        
        const data = await response.json();
        return data;
    } catch (error) {
        console.log("Failed to fetch user:", error.message);
        return null;
    }
}

Validation with Custom Errors

class ValidationError extends Error {
    constructor(field, message) {
        super(`${field}: ${message}`);
        this.name = "ValidationError";
        this.field = field;
    }
}

function validateUser(user) {
    if (!user.name) {
        throw new ValidationError("name", "Name is required");
    }
    
    if (!user.email) {
        throw new ValidationError("email", "Email is required");
    }
    
    if (user.age < 18) {
        throw new ValidationError("age", "Must be 18 or older");
    }
    
    return true;
}

try {
    validateUser({ name: "Alice", email: "[email protected]", age: 16 });
} catch (error) {
    if (error instanceof ValidationError) {
        console.log(`Validation error in ${error.field}: ${error.message}`);
    }
}

Retry Logic

async function retryOperation(fn, maxRetries = 3) {
    for (let i = 0; i < maxRetries; i++) {
        try {
            return await fn();
        } catch (error) {
            if (i === maxRetries - 1) {
                throw error; // Last attempt failed
            }
            console.log(`Attempt ${i + 1} failed, retrying...`);
            await new Promise(resolve => setTimeout(resolve, 1000));
        }
    }
}

// Usage
retryOperation(async () => {
    const response = await fetch('/api/data');
    return response.json();
});

Error Handling Best Practices

Be Specific

// Good - specific error handling
try {
    const data = JSON.parse(jsonString);
} catch (error) {
    if (error instanceof SyntaxError) {
        console.log("Invalid JSON format");
    } else {
        console.log("Unexpected error:", error);
    }
}

// Avoid - too broad
try {
    // lots of code
} catch (error) {
    console.log("Something went wrong");
}

Don’t Swallow Errors Silently

// Bad - error is ignored
try {
    riskyOperation();
} catch (error) {
    // Nothing happens
}

// Good - error is logged or handled
try {
    riskyOperation();
} catch (error) {
    console.error("Operation failed:", error);
    // Take appropriate action
}

Use Finally for Cleanup

// Good - cleanup guaranteed
try {
    // operation
} finally {
    cleanup();
}

// Avoid - cleanup might not run
try {
    // operation
}
catch (error) {
    cleanup();
    throw error;
}

Provide Context

// Good - context helps debugging
try {
    const user = await fetchUser(userId);
} catch (error) {
    throw new Error(`Failed to fetch user ${userId}: ${error.message}`);
}

// Less helpful
try {
    const user = await fetchUser(userId);
} catch (error) {
    throw error;
}

Official Documentation

External Resources

Summary

  • Try-catch-finally: fundamental error handling structure
  • Error types: SyntaxError, ReferenceError, TypeError, RangeError
  • Throw: create errors with throw new Error()
  • Custom errors: extend Error class for domain-specific errors
  • Finally: guaranteed cleanup code
  • Best practices: be specific, don’t swallow errors, provide context

Next Steps

  1. Debugging JavaScript: Tools and Techniques
  2. Custom Errors and Error Types
  3. Async/Await: Modern Asynchronous Programming
  4. Error Handling with Promises and Async/Await
  5. Testing Basics with Jest

Comments