Skip to content

JavaScript Error Handling

Error handling is a crucial part of programming that ensures programs can gracefully handle exceptions instead of crashing. JavaScript provides various error handling mechanisms, including try...catch statements, error objects, and custom errors. Mastering error handling is essential for writing robust, reliable JavaScript applications. In this chapter, we will learn about error handling in JavaScript.

What is Error Handling

Error handling refers to the mechanism that allows programs to catch, handle, and recover from exceptions during execution. Good error handling can:

  1. Prevent program crashes: Avoid stopping the entire application due to unhandled errors
  2. Provide user-friendly error messages: Help users understand what went wrong
  3. Facilitate debugging and maintenance: Provide detailed error information to help developers locate issues
  4. Improve program robustness: Enable programs to handle various exceptional situations

Error Types in JavaScript

JavaScript has several built-in error types:

1. Error (Basic Error Type)

javascript
const basicError = new Error("This is a basic error");
console.log(basicError.name);    // "Error"
console.log(basicError.message); // "This is a basic error"
console.log(basicError.stack);   // Error stack trace

2. SyntaxError

javascript
try {
    eval("const a = ;"); // Syntax error
} catch (error) {
    console.log(error.name);    // "SyntaxError"
    console.log(error.message); // "Unexpected token ';'"
}

3. ReferenceError

javascript
try {
    console.log(undefinedVariable); // Reference to undeclared variable
} catch (error) {
    console.log(error.name);    // "ReferenceError"
    console.log(error.message); // "undefinedVariable is not defined"
}

4. TypeError

javascript
try {
    const num = 123;
    num.toUpperCase(); // Numbers don't have toUpperCase method
} catch (error) {
    console.log(error.name);    // "TypeError"
    console.log(error.message); // "num.toUpperCase is not a function"
}

5. RangeError

javascript
try {
    const arr = new Array(-1); // Array length cannot be negative
} catch (error) {
    console.log(error.name);    // "RangeError"
    console.log(error.message); // "Invalid array length"
}

6. URIError

javascript
try {
    decodeURIComponent("%"); // Invalid URI component
} catch (error) {
    console.log(error.name);    // "URIError"
    console.log(error.message); // "URI malformed"
}

try...catch Statement

The try...catch statement is the most basic error handling mechanism in JavaScript.

Basic Syntax

javascript
try {
    // Code that might throw an error
    console.log("Code being attempted");
} catch (error) {
    // Error handling code
    console.log("Caught error: " + error.message);
}

Complete try...catch...finally Structure

javascript
try {
    console.log("Code being attempted");
    throw new Error("Manually thrown error");
} catch (error) {
    console.log("Caught error: " + error.message);
} finally {
    console.log("This code always executes regardless of errors");
}

Error Object Properties

javascript
try {
    throw new Error("Test error");
} catch (error) {
    console.log("Error name: " + error.name);      // "Error"
    console.log("Error message: " + error.message); // "Test error"
    console.log("Error stack: " + error.stack);     // Location information
    console.log("Is Error instance: " + (error instanceof Error)); // true
}

throw Statement

The throw statement is used to manually throw errors.

Throwing Built-in Errors

javascript
function validateAge(age) {
    if (age < 0) {
        throw new RangeError("Age cannot be negative");
    }
    
    if (age > 150) {
        throw new RangeError("Age cannot exceed 150");
    }
    
    return true;
}

try {
    validateAge(-5);
} catch (error) {
    console.log(error.name + ": " + error.message);
}

Throwing Custom Errors

javascript
function divide(a, b) {
    if (b === 0) {
        throw new Error("Cannot divide by zero");
    }
    return a / b;
}

try {
    const result = divide(10, 0);
} catch (error) {
    console.log("Calculation error: " + error.message);
}

Custom Error Types

Creating custom error types can provide more specific error information.

Extending the Error Class

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

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

// Use custom errors
function validateEmail(email) {
    if (!email.includes("@")) {
        throw new ValidationError("Invalid email format");
    }
    return true;
}

function fetchData(url) {
    // Simulate network request failure
    throw new NetworkError("Network request failed", 500);
}

// Handle different error types
try {
    validateEmail("invalid-email");
} catch (error) {
    if (error instanceof ValidationError) {
        console.log("Validation error: " + error.message);
    } else if (error instanceof NetworkError) {
        console.log("Network error: " + error.message + " (Status: " + error.statusCode + ")");
    } else {
        console.log("Unknown error: " + error.message);
    }
}

Custom Errors with Additional Properties

javascript
class ApiError extends Error {
    constructor(message, code, details = null) {
        super(message);
        this.name = "ApiError";
        this.code = code;
        this.details = details;
        this.timestamp = new Date().toISOString();
    }
}

// Use custom error with additional properties
try {
    throw new ApiError("API call failed", "API_001", {
        endpoint: "/api/users",
        method: "GET",
        userId: 123
    });
} catch (error) {
    if (error instanceof ApiError) {
        console.log("API Error:");
        console.log("Message: " + error.message);
        console.log("Code: " + error.code);
        console.log("Details: " + JSON.stringify(error.details));
        console.log("Time: " + error.timestamp);
    }
}

Asynchronous Error Handling

Error handling in asynchronous operations requires special attention.

Promise Error Handling

javascript
// Use .catch() to handle Promise errors
function fetchData() {
    return new Promise((resolve, reject) => {
        setTimeout(() => {
            reject(new Error("Data fetch failed"));
        }, 1000);
    });
}

fetchData()
    .then(data => {
        console.log("Data: " + data);
    })
    .catch(error => {
        console.log("Promise error: " + error.message);
    });

// Chained Promise error handling
function processUserData() {
    return fetchData()
        .then(data => {
            return processData(data);
        })
        .then(processedData => {
            return saveData(processedData);
        })
        .catch(error => {
            console.log("Error in chain: " + error.message);
            return { error: error.message, data: null };
        });
}

async/await Error Handling

javascript
// Use try...catch to handle async/await errors
async function handleUserData() {
    try {
        const data = await fetchData();
        const processedData = await processData(data);
        const result = await saveData(processedData);
        return result;
    } catch (error) {
        console.log("Async operation error: " + error.message);
        throw new Error("User data processing failed: " + error.message);
    }
}

// Call async function
handleUserData()
    .then(result => {
        console.log("Success: " + result);
    })
    .catch(error => {
        console.log("Final error: " + error.message);
    });

Parallel Async Operations Error Handling

javascript
// Promise.all error handling
async function fetchMultipleData() {
    try {
        const [users, posts, comments] = await Promise.all([
            fetchUsers(),
            fetchPosts(),
            fetchComments()
        ]);
        return { users, posts, comments };
    } catch (error) {
        console.log("Error in parallel operation: " + error.message);
        throw error;
    }
}

// Promise.allSettled handling (ES2020)
async function fetchMultipleDataGracefully() {
    const results = await Promise.allSettled([
        fetchUsers(),
        fetchPosts(),
        fetchComments()
    ]);
    
    const successful = results
        .filter(result => result.status === "fulfilled")
        .map(result => result.value);
    
    const errors = results
        .filter(result => result.status === "rejected")
        .map(result => result.reason);
    
    console.log("Successful results: " + successful.length);
    console.log("Number of errors: " + errors.length);
    
    return { successful, errors };
}

Global Error Handling

window.onerror (Browser Environment)

javascript
// Global JavaScript error handling
window.onerror = function(message, source, lineno, colno, error) {
    console.log("Global error caught:");
    console.log("Message: " + message);
    console.log("Source file: " + source);
    console.log("Line number: " + lineno);
    console.log("Column number: " + colno);
    console.log("Error object: " + error);
    
    // Can send error info to server for logging
    // sendErrorToServer({ message, source, lineno, colno, error });
    
    // Return true to prevent default error handling
    return true;
};

unhandledrejection (Unhandled Promise Rejection)

javascript
// Handle unhandled Promise rejections
window.addEventListener("unhandledrejection", function(event) {
    console.log("Unhandled Promise rejection:");
    console.log("Reason: " + event.reason);
    console.log("Promise: " + event.promise);
    
    // Prevent default handling (showing error in console)
    event.preventDefault();
});

Node.js Environment Global Error Handling

javascript
// Global error handling in Node.js
process.on("uncaughtException", (error) => {
    console.log("Uncaught exception: " + error.message);
    console.log(error.stack);
    
    // Log error and exit gracefully
    // logError(error);
    process.exit(1);
});

process.on("unhandledRejection", (reason, promise) => {
    console.log("Unhandled Promise rejection: " + reason);
    console.log("Promise: " + promise);
    
    // Can choose to exit process or continue running
    // process.exit(1);
});

Error Handling Best Practices

1. Early Validation and Error Checking

javascript
function calculateBMI(weight, height) {
    // Parameter validation
    if (typeof weight !== "number" || weight <= 0) {
        throw new ValidationError("Weight must be a positive number");
    }
    
    if (typeof height !== "number" || height <= 0) {
        throw new ValidationError("Height must be a positive number");
    }
    
    if (height > 3) {
        throw new ValidationError("Height unit should be meters");
    }
    
    // Calculate BMI
    const bmi = weight / (height * height);
    return Math.round(bmi * 100) / 100;
}

// Usage example
try {
    const bmi = calculateBMI(70, 1.75);
    console.log("BMI: " + bmi);
} catch (error) {
    console.log("Calculation error: " + error.message);
}

2. Provide Meaningful Error Messages

javascript
class UserService {
    static users = [];
    
    static addUser(userData) {
        // Validate required fields
        if (!userData.name) {
            throw new ValidationError("User name is required");
        }
        
        if (!userData.email) {
            throw new ValidationError("User email is required");
        }
        
        // Validate email format
        const emailRegex = /^[^\s@]+@[^\s@]+\.[^\s@]+$/;
        if (!emailRegex.test(userData.email)) {
            throw new ValidationError("Invalid email format");
        }
        
        // Check if email already exists
        if (this.users.some(user => user.email === userData.email)) {
            throw new ValidationError("Email already in use");
        }
        
        // Create user
        const user = {
            id: Date.now(),
            ...userData,
            createdAt: new Date()
        };
        
        this.users.push(user);
        return user;
    }
    
    static getUserById(id) {
        const user = this.users.find(user => user.id === id);
        if (!user) {
            throw new Error(`User with ID ${id} not found`);
        }
        return user;
    }
}

3. Error Logging

javascript
class ErrorLogger {
    static log(error, context = {}) {
        const errorInfo = {
            timestamp: new Date().toISOString(),
            message: error.message,
            name: error.name,
            stack: error.stack,
            context: context,
            userAgent: typeof navigator !== "undefined" ? navigator.userAgent : "Node.js",
            url: typeof window !== "undefined" ? window.location.href : "Node.js"
        };
        
        // Output to console in development environment
        if (process.env.NODE_ENV === "development") {
            console.error("Error log:", errorInfo);
        }
        
        // Send to server for logging
        // this.sendToServer(errorInfo);
        
        return errorInfo;
    }
    
    static sendToServer(errorInfo) {
        // Simulate sending to server
        fetch("/api/errors", {
            method: "POST",
            headers: {
                "Content-Type": "application/json"
            },
            body: JSON.stringify(errorInfo)
        }).catch(sendError => {
            console.error("Failed to send error log:", sendError);
        });
    }
}

4. Graceful Error Recovery

javascript
class DataFetcher {
    static async fetchWithRetry(url, maxRetries = 3) {
        let lastError;
        
        for (let i = 0; i < maxRetries; i++) {
            try {
                const response = await fetch(url);
                if (!response.ok) {
                    throw new Error(`HTTP ${response.status}: ${response.statusText}`);
                }
                return await response.json();
            } catch (error) {
                lastError = error;
                console.log(`Attempt ${i + 1} failed: ${error.message}`);
                
                // If not last attempt, wait before retrying
                if (i < maxRetries - 1) {
                    await this.delay(1000 * (i + 1)); // Incremental delay
                }
            }
        }
        
        // All retries failed
        throw new Error(`Failed after ${maxRetries} retries: ${lastError.message}`);
    }
    
    static delay(ms) {
        return new Promise(resolve => setTimeout(resolve, ms));
    }
}

// Use fetch with retry
DataFetcher.fetchWithRetry("https://api.example.com/data")
    .then(data => {
        console.log("Data fetched successfully:", data);
    })
    .catch(error => {
        console.log("Final failure:", error.message);
    });

5. User-Friendly Error Display

javascript
class ErrorHandler {
    static displayUserFriendlyError(error) {
        let userMessage = "An unknown error occurred. Please try again later";
        
        if (error instanceof ValidationError) {
            userMessage = error.message;
        } else if (error instanceof NetworkError) {
            userMessage = "Network connection failed. Please check your network settings";
        } else if (error.message.includes("timeout")) {
            userMessage = "Request timed out. Please try again later";
        } else if (error.message.includes("404")) {
            userMessage = "The requested resource was not found";
        } else if (error.message.includes("500")) {
            userMessage = "Server internal error. Please try again later";
        }
        
        // Display error message to user
        this.showMessage(userMessage, "error");
        
        // Log detailed error information
        console.error("Detailed error info:", error);
    }
    
    static showMessage(message, type = "info") {
        // Create error message element
        const messageElement = document.createElement("div");
        messageElement.className = `message message-${type}`;
        messageElement.textContent = message;
        
        // Add to page
        document.body.appendChild(messageElement);
        
        // Auto remove after 3 seconds
        setTimeout(() => {
            if (messageElement.parentNode) {
                messageElement.parentNode.removeChild(messageElement);
            }
        }, 3000);
    }
}

Practical Example: Form Validation

javascript
class FormValidator {
    constructor(formElement) {
        this.form = formElement;
        this.errors = new Map();
        this.init();
    }
    
    init() {
        this.form.addEventListener("submit", (event) => {
            event.preventDefault();
            this.validateAndSubmit();
        });
        
        // Real-time validation
        this.form.querySelectorAll("input, select, textarea").forEach(field => {
            field.addEventListener("blur", () => {
                this.validateField(field);
            });
        });
    }
    
    async validateAndSubmit() {
        try {
            // Clear previous errors
            this.clearErrors();
            
            // Validate all fields
            const isValid = this.validateAllFields();
            
            if (!isValid) {
                throw new ValidationError("Please fix errors in the form");
            }
            
            // Get form data
            const formData = new FormData(this.form);
            const data = Object.fromEntries(formData);
            
            // Submit data
            this.showLoading(true);
            const result = await this.submitData(data);
            this.showSuccess("Submitted successfully!");
            
            return result;
        } catch (error) {
            this.handleError(error);
        } finally {
            this.showLoading(false);
        }
    }
    
    validateField(field) {
        const fieldName = field.name;
        const value = field.value.trim();
        let errorMessage = "";
        
        // Validate based on field type
        switch (fieldName) {
            case "email":
                if (!value) {
                    errorMessage = "Email is required";
                } else if (!/^[^\s@]+@[^\s@]+\.[^\s@]+$/.test(value)) {
                    errorMessage = "Please enter a valid email address";
                }
                break;
                
            case "password":
                if (!value) {
                    errorMessage = "Password is required";
                } else if (value.length < 8) {
                    errorMessage = "Password must be at least 8 characters";
                }
                break;
                
            default:
                if (field.hasAttribute("required") && !value) {
                    errorMessage = "This field is required";
                }
        }
        
        this.setFieldError(field, errorMessage);
        return !errorMessage;
    }
    
    validateAllFields() {
        let isValid = true;
        this.form.querySelectorAll("input, select, textarea").forEach(field => {
            if (!this.validateField(field)) {
                isValid = false;
            }
        });
        return isValid;
    }
    
    setFieldError(field, errorMessage) {
        const fieldName = field.name;
        
        if (errorMessage) {
            this.errors.set(fieldName, errorMessage);
            field.classList.add("error");
        } else {
            this.errors.delete(fieldName);
            field.classList.remove("error");
        }
    }
    
    handleError(error) {
        if (error instanceof ValidationError) {
            this.showMessage(error.message, "error");
        } else {
            this.showMessage("Submission failed. Please try again later", "error");
            console.error("Form submission error:", error);
        }
        
        // Scroll to first error field
        const firstErrorField = this.form.querySelector(".error");
        if (firstErrorField) {
            firstErrorField.scrollIntoView({ behavior: "smooth", block: "center" });
            firstErrorField.focus();
        }
    }
    
    clearErrors() {
        this.errors.clear();
        this.form.querySelectorAll(".error").forEach(field => {
            field.classList.remove("error");
        });
    }
    
    showLoading(show) {
        const submitButton = this.form.querySelector("button[type='submit']");
        if (submitButton) {
            submitButton.disabled = show;
            submitButton.textContent = show ? "Submitting..." : "Submit";
        }
    }
    
    showSuccess(message) {
        this.showMessage(message, "success");
    }
    
    showMessage(message, type) {
        // Remove previous message
        const existingMessage = this.form.querySelector(".form-message");
        if (existingMessage) {
            existingMessage.remove();
        }
        
        // Create new message
        const messageElement = document.createElement("div");
        messageElement.className = `form-message message-${type}`;
        messageElement.textContent = message;
        
        // Add to top of form
        this.form.insertBefore(messageElement, this.form.firstChild);
        
        // Auto remove after 3 seconds
        setTimeout(() => {
            if (messageElement.parentNode) {
                messageElement.remove();
            }
        }, 3000);
    }
    
    async submitData(data) {
        const response = await fetch("/api/submit", {
            method: "POST",
            headers: {
                "Content-Type": "application/json"
            },
            body: JSON.stringify(data)
        });
        
        if (!response.ok) {
            const errorData = await response.json().catch(() => ({}));
            throw new Error(errorData.message || `Submission failed: ${response.status}`);
        }
        
        return await response.json();
    }
}

// Usage example
// const validator = new FormValidator(document.getElementById("myForm"));

Summary

Key points about JavaScript error handling:

  1. Error Types: Error, SyntaxError, ReferenceError, TypeError, RangeError, URIError, EvalError
  2. Basic Syntax: try...catch...finally statement
  3. Error Throwing: throw statement and custom error types
  4. Async Error Handling: Promise.catch(), async/await + try...catch
  5. Global Error Handling: window.onerror, unhandledrejection event
  6. Custom Errors: Extend Error class for specific business errors
  7. Best Practices: Early validation, meaningful error messages, error logging, graceful recovery, user-friendly display

Mastering error handling is a key skill for writing high-quality JavaScript applications. In the next chapter, we will learn about JavaScript debugging techniques.

Content is for learning and research only.