Skip to main content

Command Palette

Search for a command to run...

Error Handling in JavaScript: Try, Catch, Finally

Updated
9 min read
Error Handling in JavaScript: Try, Catch, Finally
H
CS undergrad | Tech enthusiast | Focusing on Web Dev • DSA • ML | Building skills for real-world impact

Error Handling in JavaScript: Try, Catch, Finally

Code breaks. Users do unexpected things. Networks fail. Without error handling, your app crashes. With it, you handle problems gracefully and stay in control.

Error handling isn't about preventing all mistakes. It's about deciding what to do when they happen.


What Errors Are in JavaScript

An error is when something goes wrong while code runs. JavaScript throws an error and stops.

Common Error Types

Reference Error — Using a variable that doesn't exist:

console.log(someVariable);
// ReferenceError: someVariable is not defined

Type Error — Calling a method on the wrong type:

let number = 42;
number.toUpperCase();
// TypeError: number.toUpperCase is not a function

Range Error — Value outside acceptable range:

new Array(-1);
// RangeError: Invalid array length

Without Error Handling

function loadFile(filename) {
  const data = JSON.parse(fs.readFileSync(filename, "utf8"));
  return data;
}

loadFile("missing.json");
// Error: file not found
// App crashes. User sees nothing.

With Error Handling

function loadFile(filename) {
  try {
    const data = JSON.parse(fs.readFileSync(filename, "utf8"));
    return data;
  } catch (error) {
    return { default: true };
  }
}

loadFile("missing.json");
// Returns: { default: true }
// App keeps running.

Using Try and Catch Blocks

The try block has code that might fail. The catch block handles it if it does.

Basic Structure

try {
  // Code that might throw an error
  riskyOperation();
} catch (error) {
  // Handle the error
  console.log("Something went wrong:", error.message);
}

Real Example: Parsing JSON

let jsonString = '{ "name": "Alice" }';

try {
  let data = JSON.parse(jsonString);
  console.log("Parsed successfully:", data);
} catch (error) {
  console.log("Invalid JSON:", error.message);
}

If it's valid JSON, it parses. If not, catch handles it.

With Broken JSON

let jsonString = '{ "name": "Alice"';  // Missing closing brace

try {
  let data = JSON.parse(jsonString);
  console.log("Parsed:", data);
} catch (error) {
  console.log("Invalid JSON:", error.message);
  // Output: Invalid JSON: Unexpected end of JSON input
}

The error happens in try. Instead of crashing, catch handles it.

The Error Object

The error object has useful properties:

try {
  nonexistentFunction();
} catch (error) {
  console.log(error.message);  // The message
  console.log(error.name);     // Error type
  console.log(error.stack);    // Full trace
}

Multiple Operations

try {
  const data = fs.readFileSync("data.json", "utf8");
  const parsed = JSON.parse(data);
  processData(parsed);
} catch (error) {
  console.log("Error:", error.message);
}

If any operation fails, catch handles it.


The Finally Block

The finally block always runs, no matter what. Success or failure, it executes.

Structure

try {
  // Code that might fail
} catch (error) {
  // Handle the error
} finally {
  // Always runs
}

Real Example: Close a Database

const db = openDatabase("app.db");

try {
  const user = db.query("SELECT * FROM users WHERE id = 1");
  console.log("User found:", user);
} catch (error) {
  console.log("Query failed:", error.message);
} finally {
  db.close();  // Always close, success or failure
}

The database closes whether the query works or fails.

Execution Order

try block runs
  |
  ├─ Success → skip catch → go to finally
  └─ Failure → catch runs → then finally

finally always runs last

Resource Cleanup

function processFile(filename) {
  let file;
  
  try {
    file = openFile(filename);
    const content = file.read();
    console.log("File processed");
  } catch (error) {
    console.log("Error:", error.message);
  } finally {
    if (file) {
      file.close();  // Clean up
    }
  }
}

The file closes whether processing succeeds or fails.


Try → Catch → Finally Execution Flow

START
  |
  v
TRY BLOCK
  |
  ├─── No error
  |      |
  |      v
  |    Code runs successfully
  |      |
  |      v
  |    SKIP catch block
  |      |
  └──────┤
  |      |
  ├─── Error occurs
  |      |
  |      v
  |    Jump to CATCH block
  |      |
  |      v
  |    Handle error
  |      |
  └──────┤
  |      |
  v      v
FINALLY BLOCK (always runs)
  |
  v
END

Throwing Custom Errors

When something is wrong with your logic, create your own error.

Throw Statement

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

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

Error Types You Can Throw

throw new Error("Something went wrong");
throw new TypeError("Expected a number");
throw new RangeError("Value out of range");
throw "Quick error message";

Custom Error Classes

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

function validateEmail(email) {
  if (!email.includes("@")) {
    throw new ValidationError("Invalid email");
  }
  return email;
}

try {
  validateEmail("notanemail");
} catch (error) {
  if (error instanceof ValidationError) {
    console.log("Email error:", error.message);
  }
}

When to Throw

function divideNumbers(a, b) {
  if (typeof a !== "number" || typeof b !== "number") {
    throw new TypeError("Both must be numbers");
  }
  
  if (b === 0) {
    throw new Error("Cannot divide by zero");
  }
  
  return a / b;
}

try {
  divideNumbers(10, 2);   // Works: 5
  divideNumbers(10, 0);   // Throws error
} catch (error) {
  console.log("Math error:", error.message);
}

Why Error Handling Matters

1. Graceful Failure

Without error handling, your app crashes:

// Crashes if database fails
app.get("/user/:id", (req, res) => {
  const user = db.findUser(req.params.id);
  res.json(user);
});

// Handles the error gracefully
app.get("/user/:id", (req, res) => {
  try {
    const user = db.findUser(req.params.id);
    res.json(user);
  } catch (error) {
    res.status(500).json({ error: "User not found" });
  }
});

User gets a message instead of a blank screen.

2. Better Debugging

Error objects show you where the problem is:

try {
  riskyFunction();
} catch (error) {
  console.log(error.stack);
  // Shows file, line number, and function names
  // Helps you find bugs fast
}

3. Resource Cleanup

With finally, resources always get cleaned up:

let connection;

try {
  connection = openConnection();
} catch (error) {
  console.log("Error:", error.message);
} finally {
  if (connection) {
    connection.close();  // Don't leak resources
  }
}

Without finally, connections stay open if an error happens.

4. Better User Experience

Handle errors cleanly:

function saveData(data) {
  try {
    validateData(data);
    db.save(data);
    return { success: true };
  } catch (error) {
    return { 
      success: false, 
      message: "Could not save. Please try again." 
    };
  }
}

Users see a clear message, not a crash.


Real-World Example: Parsing User Input

function processUserInput(jsonString) {
  try {
    // Parse the JSON
    const data = JSON.parse(jsonString);
    
    // Validate the data
    if (!data.name || !data.email) {
      throw new Error("Missing required fields");
    }
    
    // Process the data
    const result = {
      name: data.name.trim(),
      email: data.email.toLowerCase()
    };
    
    console.log("Processing successful:", result);
    return result;
    
  } catch (error) {
    if (error instanceof SyntaxError) {
      console.log("Invalid JSON format:", error.message);
    } else if (error.message.includes("required")) {
      console.log("Validation error:", error.message);
    } else {
      console.log("Unexpected error:", error.message);
    }
    
    return null;
    
  } finally {
    console.log("Processing complete");
  }
}

// Test it
processUserInput('{ "name": "Alice", "email": "alice@example.com" }');
// Output: Processing successful: { name: 'Alice', email: 'alice@example.com' }
//         Processing complete

processUserInput('invalid json');
// Output: Invalid JSON format: Unexpected token i...
//         Processing complete

processUserInput('{ "name": "Bob" }');
// Output: Validation error: Missing required fields
//         Processing complete

Each scenario is handled appropriately.


Error Handling Patterns

Pattern 1: Try-Catch-Finally

try {
  // Do something
} catch (error) {
  // Handle error
} finally {
  // Cleanup
}

Pattern 2: Catch and Rethrow

try {
  database.query("SELECT * FROM users");
} catch (error) {
  console.log("Database error:", error);
  throw error;  // Pass it up to the caller
}

Pattern 3: Catch and Return Default

function getConfig() {
  try {
    return loadConfig();
  } catch (error) {
    return defaultConfig;  // Use defaults if load fails
  }
}

Pattern 4: Catch Specific Errors

try {
  operation();
} catch (error) {
  if (error instanceof TypeError) {
    console.log("Type error:", error.message);
  } else if (error instanceof RangeError) {
    console.log("Range error:", error.message);
  } else {
    console.log("Unknown error:", error.message);
  }
}

Practice Assignment

1. Parse JSON safely:

const jsonString = '{ "name": "Alice", "age": 25 }';

// Write a function that parses the JSON
// If parsing fails, return a default object

2. Handle multiple errors:

function divideAndLog(a, b) {
  try {
    // Check if both are numbers
    // Check if b is not zero
    // Divide and log result
  } catch (error) {
    // Handle the error
  }
}

3. Create a custom error:

// Create a PasswordError class
// Throw it if password is too short
// Catch it and show a message

4. Use finally for cleanup:

let file = null;

try {
  file = openFile("data.txt");
  // Read and process the file
} catch (error) {
  // Handle error
} finally {
  // Make sure file closes
}

Quick Recap

  • Errors happen when code breaks. JavaScript throws and stops.

  • Try block contains code that might fail.

  • Catch block handles errors if they happen.

  • Finally block always runs, success or failure.

  • Throw keyword creates your own errors.

  • Error objects have .message, .stack, and .name.

  • Error handling lets you fail gracefully instead of crashing.

  • Use finally to clean up resources.

  • Catch specific error types for different handling.

  • Error messages help you debug faster.

  • Good error handling improves user experience.

Error handling isn't optional. It's how you control what happens when things go wrong.

Happy coding! 🚀


If you enjoyed this article, check out my other blogs on this profile. 🔗 Connect with me: LinkedIn | GitHub | X (Twitter)