Home
 › Learn
 › JavaScript

Learn JavaScript Error Handling with try, catch, finally

A comprehensive guide to handling errors gracefully in JavaScript using try-catch-finally blocks, custom errors, and best practices.

Updated At: December 2021
Learn JavaScript Error Handling with try, catch, finally

Here is the complete list of JavaScript Error Handling methods categorized by their primary functions.

What is Error Handling?

Error handling is the process of anticipating, detecting, and managing errors that occur during program execution. Without proper error handling, unexpected errors can crash your application. JavaScript provides the try...catch...finally statement to handle errors gracefully.

FeaturePurpose
tryWraps code that might throw an error
catchHandles the error if one is thrown
finallyRuns cleanup code regardless of error
throwManually throws an error
Custom ErrorsCreate domain-specific error classes
Global handlersCatch unhandled errors application-wide


⁠Proper error handling makes your applications robust, maintainable, and user-friendly. Always anticipate potential errors and handle them gracefully!

The try...catch Block

The try...catch statement allows you to test code for errors and handle them without stopping execution.

Basic Syntax:

try {
  // Code that might throw an error
} catch (error) {
  // Code to handle the error
}


Simple Example:

try {
  const result = 10 / 2;
  console.log(result); // 5
} catch (error) {
  console.log("An error occurred:", error.message);
}


Example with an Error:

try {
  const obj = null;
  console.log(obj.name); // TypeError: Cannot read property 'name' of null
} catch (error) {
  console.log("Caught error:", error.message); // "Cannot read property 'name' of null"
}


Error Object Properties

When an error is caught, the error object contains useful information:

  • name - The type of error (e.g., TypeError, ReferenceError, SyntaxError)

  • message - A description of the error

  • stack - The stack trace showing where the error occurred

Example:

try {
  throw new Error("Something went wrong!");
} catch (error) {
  console.log("Name:", error.name);    // "Error"
  console.log("Message:", error.message); // "Something went wrong!"
  console.log("Stack:", error.stack);  // Full stack trace
}


⁠Built-in Error Types

JavaScript has several built-in error types:


⁠ReferenceError

Thrown when referencing an undefined variable.

try {
  console.log(undefinedVariable);
} catch (error) {
  console.log(error.name); // "ReferenceError"
}


⁠TypeError

Thrown when performing an invalid operation on a value.

try {
  const str = "hello";
  str.forEach(char => console.log(char)); // strings don't have forEach
} catch (error) {
  console.log(error.name); // "TypeError"
}


⁠SyntaxError

Thrown when invalid code syntax is encountered (usually caught during parsing).

// This would be caught by the parser, not try-catch
// const x = {invalid syntax here};


⁠RangeError

Thrown when a numeric value is out of range.

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


⁠SyntaxError (eval)

When using eval() with invalid code:

try {
  eval("const x = {invalid}");
} catch (error) {
  console.log(error.name); // "SyntaxError"
}


⁠The finally Block

The finally block executes regardless of whether an error occurred. Use it for cleanup operations like closing files, releasing resources, or logging.

Syntax:

try {
  // Code that might throw an error
} catch (error) {
  // Handle the error
} finally {
  // Code that always runs
}


⁠Example:

let file = null;

try {
  file = openFile("data.txt");
  console.log("File opened successfully");
  throw new Error("Processing failed");
} catch (error) {
  console.log("Error:", error.message);
} finally {
  if (file) {
    closeFile(file);
    console.log("File closed");
  }
}

// Output:
// Error: Processing failed
// File closed


Throwing Custom Errors

You can throw your own errors using the throw statement.

Basic Example:

function validateAge(age) {
  if (age < 0 || age > 150) {
    throw new Error("Invalid age");
  }
  return "Age is valid";
}

try {
  console.log(validateAge(-5));
} catch (error) {
  console.log("Validation error:", error.message); // "Validation error: Invalid age"
}


⁠Throwing with Specific Error Types:

function validateEmail(email) {
  if (!email.includes("@")) {
    throw new TypeError("Email must contain @");
  }
  return "Email is valid";
}

try {
  validateEmail("invalidemail.com");
} catch (error) {
  if (error instanceof TypeError) {
    console.log("Type error:", error.message);
  }
}


⁠Creating Custom Error Classes

Create custom error classes for domain-specific errors.

Example:

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

class NotFoundError extends Error {
  constructor(resource) {
    super(`${resource} not found`);
    this.name = "NotFoundError";
  }
}

try {
  throw new ValidationError("Invalid input provided");
} catch (error) {
  console.log(error.name);    // "ValidationError"
  console.log(error.message); // "Invalid input provided"
}

try {
  throw new NotFoundError("User");
} catch (error) {
  console.log(error.name);    // "NotFoundError"
  console.log(error.message); // "User not found"
}


Nested try...catch Blocks

You can nest try...catch blocks to handle different error scenarios.

Example:

try {
  try {
    throw new Error("Inner error");
  } catch (innerError) {
    console.log("Inner catch:", innerError.message);
    throw new Error("Re-thrown error");
  }
} catch (outerError) {
  console.log("Outer catch:", outerError.message);
}

// Output:
// Inner catch: Inner error
// Outer catch: Re-thrown error


Error Handling in Promises

When working with promises, use .catch() or try...catch with async/await.

Using .catch():

fetch("https://api.example.com/data")
  .then(response => response.json())
  .then(data => console.log(data))
  .catch(error => {
    console.log("Fetch error:", error.message);
  });


⁠Using async/await with try...catch:

async function fetchData() {
  try {
    const response = await fetch("https://api.example.com/data");
    if (!response.ok) {
      throw new Error(`HTTP error! status: ${response.status}`);
    }
    const data = await response.json();
    console.log(data);
  } catch (error) {
    console.log("Error fetching data:", error.message);
  }
}

fetchData();


Error Handling with Async/Await

Example – Multiple async operations:

async function loadUserData() {
  try {
    const userResponse = await fetch("/api/user");
    const user = await userResponse.json();
    
    const postsResponse = await fetch(`/api/posts/${user.id}`);
    const posts = await postsResponse.json();
    
    console.log("User:", user);
    console.log("Posts:", posts);
  } catch (error) {
    console.log("Error loading data:", error.message);
  } finally {
    console.log("Data loading complete");
  }
}

loadUserData();


Global Error Handler

Catch unhandled errors globally using window.onerror or window.onunhandledrejection.

For synchronous errors:

window.onerror = function(message, source, lineno, colno, error) {
  console.log("Global error caught:", message);
  console.log("Source:", source);
  console.log("Line:", lineno);
  return true; // Prevents default error handling
};


⁠For unhandled promise rejections:

window.onunhandledrejection = function(event) {
  console.log("Unhandled promise rejection:", event.reason);
};


Practical Examples

Form Validation Error Handling

function validateForm(formData) {
  try {
    if (!formData.name || formData.name.trim() === "") {
      throw new Error("Name is required");
    }
    if (!formData.email || !formData.email.includes("@")) {
      throw new Error("Valid email is required");
    }
    if (!formData.password || formData.password.length < 8) {
      throw new Error("Password must be at least 8 characters");
    }
    return { success: true, data: formData };
  } catch (error) {
    return { success: false, error: error.message };
  }
}

const result = validateForm({ name: "John", email: "john@example.com", password: "short" });
console.log(result);
// { success: false, error: "Password must be at least 8 characters" }


⁠API Call with Retry Logic

async function fetchWithRetry(url, maxRetries = 3) {
  for (let attempt = 1; attempt <= maxRetries; attempt++) {
    try {
      const response = await fetch(url);
      if (!response.ok) {
        throw new Error(`HTTP ${response.status}`);
      }
      return await response.json();
    } catch (error) {
      console.log(`Attempt ${attempt} failed:`, error.message);
      if (attempt === maxRetries) {
        throw new Error(`Failed after ${maxRetries} attempts`);
      }
      await new Promise(resolve => setTimeout(resolve, 1000 * attempt)); // Exponential backoff
    }
  }
}

fetchWithRetry("https://api.example.com/data")
  .then(data => console.log("Success:", data))
  .catch(error => console.log("Final error:", error.message));


⁠JSON Parsing with Error Handling

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

const data1 = safeJSONParse('{"name":"John"}');
console.log(data1); // { name: "John" }

const data2 = safeJSONParse('invalid json', {});
console.log(data2); // {}


Best Practices

  1. Be specific: Catch specific errors rather than all errors indiscriminately.

  2. Use finally for cleanup: Always use finally for resource cleanup (closing files, connections).

  3. Log errors properly: Log errors with context for debugging (include timestamps, user info, etc.).

  4. Re-throw when appropriate: Don't silently swallow errors; re-throw or handle them meaningfully.

  5. Provide user feedback: Show user-friendly error messages, not technical jargon.

  6. Validate input: Prevent errors by validating input early.

  7. Use custom error classes: Create specific error types for different scenarios.

  8. Test error paths: Write tests for error handling code.

  9. Use async/await with try...catch: It's more readable than promise chains.

  10. Implement global error handlers: Catch unhandled errors at the application level.

JavaScript

    React

      NextJS

        HTML

          CSS

            Sign up for our newsletter.

            Copyright © theHardCoder 2021 - 2025