Error Handling in JavaScript – Writing Robust and Safe Code

Understanding Errors and Exception Handling in JavaScript

No matter how carefully we write our code, errors are inevitable. Proper error handling is crucial to make applications stable, user-friendly, and easier to debug. JavaScript provides several powerful mechanisms for handling errors gracefully.

In this module, we’ll cover error types, handling techniques, and best practices.


Table of Contents

  1. Types of Errors in JavaScript
  2. What is Exception Handling?
  3. try…catch Statement
  4. The throw Keyword
  5. Error Object and Custom Errors
  6. finally Block
  7. Asynchronous Error Handling
  8. Debugging with DevTools
  9. Best Practices
  10. Conclusion

1. Types of Errors in JavaScript

There are three major categories of errors:

  • Syntax Errors – mistakes in code structure. javascriptCopyEditconsole.log("Hello" // SyntaxError: missing )
  • Runtime Errors – occur during execution. javascriptCopyEditlet a = b + 2; // ReferenceError: b is not defined
  • Logical Errors – incorrect output, no error thrown. javascriptCopyEditfunction isEven(n) { return n % 2 === 1; // wrong logic! }

2. What is Exception Handling?

Exception handling is a way to deal with unexpected events during program execution without crashing the entire app.


3. try…catch Statement

The try...catch block is used to catch and handle errors gracefully.

try {
let result = riskyFunction();
console.log(result);
} catch (error) {
console.error("Something went wrong:", error.message);
}
  • Code in the try block is executed.
  • If an error occurs, control jumps to the catch block.

4. The throw Keyword

You can throw custom errors using throw.

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

try {
divide(5, 0);
} catch (e) {
console.error(e.message);
}

You can throw anything (strings, objects), but using the Error constructor is recommended.


5. Error Object and Custom Errors

Built-in Error Types:

  • Error
  • SyntaxError
  • ReferenceError
  • TypeError
  • RangeError

Creating Custom Errors:

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

throw new ValidationError("Invalid input");

6. finally Block

Code in finally always runs, regardless of whether an error occurred.

try {
// code
} catch (e) {
// handle error
} finally {
console.log("Cleanup actions here");
}

Useful for closing resources, stopping loaders, etc.


7. Asynchronous Error Handling

In async functions or Promises, errors must be caught with .catch() or try...catch in async/await.

Promises:

fetch("invalid-url")
.then(res => res.json())
.catch(error => console.error("Fetch failed:", error));

Async/Await:

async function loadData() {
try {
const res = await fetch("invalid-url");
const data = await res.json();
} catch (error) {
console.error("Error fetching:", error.message);
}
}

8. Debugging with DevTools

Modern browsers come with developer tools for debugging:

  • Use breakpoints to pause execution.
  • Watch variable values in real time.
  • View call stack and scopes.

Add debugger; in your code to trigger the debugger:

function test() {
let a = 5;
debugger;
console.log(a);
}

9. Best Practices

  • Always validate user inputs.
  • Avoid swallowing errors silently.
  • Use meaningful error messages.
  • Use try...catch wisely — not everywhere.
  • Prefer async/await with try...catch over .then().catch() for readability.
  • Log errors for diagnostics, especially in production.

10. Conclusion

Robust error handling is vital for building professional JavaScript applications. It not only prevents crashes but also helps developers debug and improve UX.

In the next module, we’ll dive into JavaScript Modules (import/export) — a powerful way to organize and reuse code.