Skip to main content

Error Handling & Custom Errors

Introduction​

Errors are part of programming — things don’t always go as expected. Maybe a file doesn’t exist, a network request fails, or your code receives the wrong type of data. If errors are not handled properly, your program may crash.

JavaScript provides tools to handle errors gracefully so your program can recover or at least fail in a controlled way.

  • Use throw to create and signal an error.
  • Use try...catch to handle errors.
  • Use finally for cleanup code that always runs.
  • You can also create custom error classes to describe specific problems.

Built-in error types in JavaScript include:

  • Error (generic)
  • TypeError (wrong type, e.g. calling a non-function)
  • ReferenceError (using an undefined variable)
  • RangeError (invalid range, e.g. negative array length)
  • SyntaxError (invalid JavaScript code)

Understanding how to throw, catch, and define errors makes your code more robust and easier to debug.


Concepts​

Throwing Errors

  • Use throw to create an error.
  • Can throw built-in error objects or custom ones.
throw new Error("Something went wrong");

Try...Catch

  • Wrap risky code in try.
  • Handle errors in catch.
  • Optionally use finally for cleanup.
try {
let result = riskyFunction();
console.log(result);
} catch (err) {
console.error("Caught an error:", err.message);
} finally {
console.log("This always runs");
}

Error Handling with Promises

fetchData()
.then(data => console.log(data))
.catch(err => console.error("Promise error:", err));

Error Handling with Async/Await

async function run() {
try {
const data = await fetchData();
console.log(data);
} catch (err) {
console.error("Async error:", err.message);
}
}

Custom Error Classes

  • Extend the built-in Error class.
  • Provide meaningful names and messages.
class ValidationError extends Error {
constructor(message) {
super(message);
this.name = "ValidationError";
}
}

function checkAge(age) {
if (age < 18) {
throw new ValidationError("Age must be 18 or older");
}
return "Valid age";
}

try {
console.log(checkAge(15));
} catch (err) {
if (err instanceof ValidationError) {
console.error("Validation failed:", err.message);
} else {
console.error("Unknown error:", err);
}
}

Examples​

Built-in Error Types

  • Error (generic)
  • TypeError (wrong type of value)
  • ReferenceError (variable not defined)
  • RangeError (value out of range)
try {
let arr = new Array(-1); // ❌
} catch (err) {
console.log(err.name); // RangeError
console.log(err.message); // Invalid array length
}

Challenges​

1. Division by zero​

  • Write a function that divides two numbers.
  • If the divisor is 0, throw an error.
  • Catch it and print "Division by zero not allowed".
💡 Show Answer
function divide(a, b) {
if (b === 0) throw new Error("Division by zero not allowed");
return a / b;
}
try {
console.log(divide(5, 0));
} catch (e) {
console.error(e.message);
}

2. Parse JSON safely​

  • Create a function parseJSON(str) that tries to parse a string with JSON.parse.
  • If it fails, catch the error and print "Invalid JSON".
💡 Show Answer
function parseJSON(str) {
try {
return JSON.parse(str);
} catch (error) {
console.log("Invalid JSON");
return null;
}
}

console.log(parseJSON('{"name": "Alice"}')); // { name: 'Alice' }
console.log(parseJSON("Invalid JSON")); // Invalid JSON \n null

3. Throw TypeError​

  • Write a function that throws a TypeError if the input is not a number.
💡 Show Answer
function square(n) {
if (typeof n !== "number") throw new TypeError("You must provide a number");
return n * n;
}

try {
console.log(square("2"));
} catch (e) {
console.error(e.message);
}

4. Use finally​

  • Demonstrate the use of finally by cleaning up a resource (e.g., logging "Cleanup done").
💡 Show Answer
try{throw new Error("err");}catch(e){console.log(e.message);}finally{console.log("clean");}

5. Promise rejects with error​

  • Create a promise that rejects with an error.
  • Handle it with .catch().
💡 Show Answer
Promise.reject(new Error("Promise rejected")).catch((e) =>
console.log(e.message)
);

6. Same with async/await​

  • Rewrite the same promise using async/await with try...catch.
💡 Show Answer
async function run() {
try {
await Promise.reject(new Error("Promise rejected"));
} catch (e) {
console.log(e.message);
}
}

run();

7. Custom ValidationError​

  • Define a custom error ValidationError.
  • Throw it when a string is empty.
  • Catch and handle it.
💡 Show Answer
class ValidationError extends Error {
constructor(message) {
super(message);
this.name = "ValidationError";
}
}

function validateString(str) {
if (str === "") {
throw new ValidationError("String cannot be empty.");
}
return true;
}

try {
validateString(""); // This will throw
} catch (error) {
if (error instanceof ValidationError) {
console.error("Validation error caught:", error.message);
} else {
throw error;
}
}

8. DatabaseError​

  • Extend Error to create a DatabaseError.
  • Simulate a failing database query and throw the error.
💡 Show Answer
class DatabaseError extends Error {
constructor(message) {
super(message);
this.name = "DatabaseError";
}
}

// Simulate a failing database query
function queryDatabase() {
const success = false; // Simulate failure
if (!success) {
throw new DatabaseError("Database query failed.");
}
return "Query result";
}

try {
queryDatabase();
} catch (err) {
if (err instanceof DatabaseError) {
console.error(`Custom error caught: ${err.name} - ${err.message}`);
} else {
console.error("Unknown error:", err);
}
}

9. MathError safeDivide​

💡 Show Answer
class MathError extends Error {
constructor(message) {
super(message);
this.name = "MathError";
}
}

function safeDivide(a, b) {
if (b === 0) {
throw new MathError("Division by zero is not allowed.");
}
return a / b;
}

// Example usage:
try {
const result = safeDivide(10, 0);
console.log("Result:", result);
} catch (error) {
if (error instanceof MathError) {
console.error("MathError:", error.message);
} else {
console.error("Unexpected error:", error);
}
}

10. Multi-error demo​

  • Create a small demo where multiple errors might occur (e.g., invalid input, division by zero).
  • Catch each type separately and print meaningful messages.
💡 Show Answer
function demoErrorHandling(input1, input2) {
try {
if (typeof input1 !== "number" || typeof input2 !== "number") {
throw new TypeError("Both inputs must be numbers.");
}
if (input2 === 0) {
throw new RangeError("Division by zero is not allowed.");
}
let result = input1 / input2;
console.log(`Result: ${result}`);
} catch (error) {
if (error instanceof TypeError) {
console.error(`Type Error: ${error.message}`);
} else if (error instanceof RangeError) {
console.error(`Range Error: ${error.message}`);
} else {
console.error(`Unknown Error: ${error.message}`);
}
}
}

// Demo calls
demoErrorHandling(10, 2); // Valid input
demoErrorHandling("a", 2); // Type error
demoErrorHandling(10, 0); // Range error
demoErrorHandling(10, "b"); // Type error