Skip to main content

Error Handling in JavaScript

Published: December 18, 2025 Updated: May 8, 2026 Larry Qu 5 min read

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

Resources

Comments

👍 Was this article helpful?