Error Handling · learncode.live

Error Handling

Error Handling, “try…catch”

Errors happen. JavaScript provides try...catch to handle them gracefully.

Basic Syntax

try {
  // Code that might throw an error
  let result = JSON.parse("{invalid json}");
  console.log("This won't run");
} catch (error) {
  // Handle the error
  console.log("Error caught:", error.message);
} finally {
  // Always runs (optional)
  console.log("Cleanup code");
}

How it Works

try {
  console.log("Start of try");
  undefinedFunction(); // This throws an error
  console.log("End of try"); // Never reached
} catch (err) {
  console.log("Error:", err.message); // → "Error: undefinedFunction is not defined"
}

console.log("Code continues..."); // → "Code continues..."

Only Runtime Errors

try...catch only catches runtime errors - not syntax errors:

try {
  // Syntax error - not caught (script stops)
  {{{{{{{{;
} catch (err) {
  // Never reached
}

Error Object

The catch block receives an error object with properties:

try {
  throw new Error("Something went wrong");
} catch (err) {
  console.log(err.name);    // → "Error"
  console.log(err.message); // → "Something went wrong"
  console.log(err.stack);   // → Stack trace (string)
}

Throwing Our Own Errors

Use throw to generate an error intentionally:

let age = 15;

try {
  if (age < 18) {
    throw new Error("Access denied - too young");
  }
  console.log("Welcome");
} catch (err) {
  console.log(err.message); // → "Access denied - too young"
}

Throw Anything

You can throw any value, but throwing Error objects is best practice:

throw "Error string";        // String
throw 42;                    // Number
throw true;                  // Boolean
throw { code: 404, msg: "Not Found" }; // Object
throw new Error("Error");    // Error object (recommended)

try…catch…finally

finally always runs, regardless of whether an error occurred:

function processData(data) {
  try {
    console.log("Processing...");
    if (!data) throw new Error("No data");
    console.log("Success:", data);
    return "Done";
  } catch (err) {
    console.log("Error:", err.message);
    return "Failed";
  } finally {
    console.log("Cleanup - always runs");
  }
}

console.log(processData("test"));
// → "Processing..."
// → "Success: test"
// → "Cleanup - always runs"
// → "Done"

console.log(processData(null));
// → "Processing..."
// → "Error: No data"
// → "Cleanup - always runs"
// → "Failed"

Even return in try/catch doesn’t prevent finally from running.

Nested try…catch

try {
  try {
    throw new Error("Inner error");
  } catch (innerErr) {
    console.log("Inner catch:", innerErr.message);
    throw new Error("Rethrown from inner"); // Rethrow
  }
} catch (outerErr) {
  console.log("Outer catch:", outerErr.message);
}
// → "Inner catch: Inner error"
// → "Outer catch: Rethrown from inner"

Rethrowing

Sometimes you want to handle only certain errors and rethrow others:

try {
  let data = JSON.parse(userInput);
  process(data);
} catch (err) {
  if (err instanceof SyntaxError) {
    console.log("Invalid JSON:", err.message);
  } else {
    throw err; // Unknown error - rethrow
  }
}

Global Error Handler

// Browser
window.onerror = function(message, url, line, col, error) {
  console.log("Global error:", message);
  return true; // Prevent default handling
};

// Node.js
process.on("uncaughtException", (err) => {
  console.error("Uncaught:", err);
  process.exit(1);
});
Demo: Error Handling Demo
JavaScript
function divide(a, b) {
if (b === 0) {
throw new Error('Division by zero!');
}
return a / b;
}

function safeDivide(a, b) {
try {
let result = divide(a, b);
return 'Result: ' + result;
} catch (err) {
return 'Error: ' + err.message;
} finally {
document.write('(Operation attempted: ' + a + ' / ' + b + ')<br>');
}
}

document.write(safeDivide(10, 2) + '<br>');
document.write(safeDivide(10, 0) + '<br>');
document.write(safeDivide(20, 4) + '<br>');
Live Output Window

Custom Errors, Extending Error

Create custom error types for specific error scenarios:

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

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

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

Using Custom Errors

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

function saveUser(user) {
  if (!navigator.onLine) {
    throw new NetworkError(503, "Service unavailable");
  }
  // Save to server...
}

try {
  let user = { name: "", age: -1 };
  validateUser(user);
  saveUser(user);
} catch (err) {
  if (err instanceof ValidationError) {
    console.log(`Validation failed for ${err.field}: ${err.message}`);
  } else if (err instanceof NetworkError) {
    console.log(`Network error (${err.statusCode}): ${err.message}`);
  } else {
    console.log("Unknown error:", err);
  }
}
// → "Validation failed for name: Name is required"

Error Hierarchy

// Base error
class AppError extends Error {
  constructor(message) {
    super(message);
    this.name = this.constructor.name;
  }
}

// Specific errors
class ValidationError extends AppError {}
class NotFoundError extends AppError {
  constructor(resource, id) {
    super(`${resource} with id '${id}' not found`);
    this.resource = resource;
    this.id = id;
  }
}
class PermissionError extends AppError {
  constructor(action) {
    super(`Permission denied: ${action}`);
    this.action = action;
  }
}

// Usage with instanceof hierarchy
try {
  throw new NotFoundError("User", 42);
} catch (err) {
  if (err instanceof NotFoundError) {
    console.log(err.message); // → "User with id '42' not found"
  } else if (err instanceof AppError) {
    console.log("App error:", err.message);
  } else {
    console.log("Unknown error");
  }
}

Wrapping Errors

Sometimes you want to add context to an error:

class DatabaseError extends Error {
  constructor(operation, originalError) {
    super(`Database ${operation} failed: ${originalError.message}`);
    this.name = "DatabaseError";
    this.originalError = originalError;
  }
}

try {
  try {
    // Database operation
    throw new Error("Connection timeout");
  } catch (err) {
    throw new DatabaseError("insert", err);
  }
} catch (err) {
  console.log(err.message);     // → "Database insert failed: Connection timeout"
  console.log(err.originalError); // → The original error
}
Demo: Custom Errors Demo
JavaScript
class AppError extends Error {
constructor(message) {
super(message);
this.name = this.constructor.name;
}
}

class NegativeNumberError extends AppError {
constructor(value) {
super('Negative numbers not allowed: ' + value);
this.value = value;
}
}

function squareRoot(n) {
if (n < 0) {
throw new NegativeNumberError(n);
}
return Math.sqrt(n);
}

function calculate(n) {
try {
let result = squareRoot(n);
return '√' + n + ' = ' + result.toFixed(4);
} catch (err) {
if (err instanceof NegativeNumberError) {
return 'Error: ' + err.message;
}
return 'Unknown error';
}
}

document.write(calculate(16) + '<br>');
document.write(calculate(-4) + '<br>');
document.write(calculate(25) + '<br>');
document.write(calculate(-9) + '<br>');
Live Output Window
Courses