DEV Community

Zakir
Zakir

Posted on

Level Up Your JavaScript Error Handling: From `try...catch` to Custom Errors

Error handling is one of those things every JavaScript developer encounters, but not everyone dives deep into mastering it. If you’ve been relying on simple try...catch statements, it's time to elevate your game. This post will take you through the journey from basic error handling to crafting custom errors, making your code more resilient and easier to debug.

1. The Basics: try...catch

Let's start with the classic try...catch:

try {
  const data = JSON.parse('{"name": "John"}');
  console.log(data.age.toUpperCase()); // This will cause an error
} catch (error) {
  console.error("Something went wrong:", error);
}
Enter fullscreen mode Exit fullscreen mode

This is simple and effective. The try block lets you run code that might throw an error, and the catch block captures it, allowing you to handle it gracefully.

However, as your application grows, relying solely on this can lead to less informative error messages, which makes debugging a nightmare. This is where custom error handling shines.

2. Understanding Native Error Types

JavaScript comes with built-in error types, such as:

  • Error: The generic error object.
  • TypeError: Thrown when a variable is not of the expected type.
  • SyntaxError: Thrown when code contains a syntax mistake.
  • ReferenceError: Thrown when referencing a non-existent variable.

Example:

try {
  let result = myUndefinedFunction();
} catch (error) {
  if (error instanceof ReferenceError) {
    console.error("ReferenceError detected:", error.message);
  } else {
    console.error("Unexpected error:", error);
  }
}
Enter fullscreen mode Exit fullscreen mode

By utilizing these types, you can start building more descriptive error handling paths. But what if you want to define errors specific to your application logic? That's where custom errors come in.

3. Creating Custom Errors

Why Custom Errors?

Custom errors help in identifying specific issues within your code, making it much easier to debug. For example, if you're building an API, you might want to differentiate between ValidationError, AuthenticationError, or DatabaseError.

How to Create Custom Errors

Let’s create a ValidationError as an example:

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

try {
  const age = -5;
  if (age < 0) {
    throw new ValidationError("Age cannot be negative!");
  }
} catch (error) {
  if (error instanceof ValidationError) {
    console.error("Validation Error:", error.message);
  } else {
    console.error("Unexpected error:", error);
  }
}
Enter fullscreen mode Exit fullscreen mode

By extending the Error class, we're able to create a more meaningful ValidationError. This approach gives clarity about the nature of the problem, making debugging easier and your code cleaner.

4. Enriching Custom Errors with More Information

Why stop at just a custom message? Let’s enrich our custom error with additional properties like errorCode or statusCode.

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

try {
  const response = { status: 404 };
  if (response.status === 404) {
    throw new HttpError(404, "Resource not found");
  }
} catch (error) {
  if (error instanceof HttpError) {
    console.error(`Error ${error.statusCode}: ${error.message}`);
  } else {
    console.error("Unexpected error:", error);
  }
}
Enter fullscreen mode Exit fullscreen mode

This way, you can capture more context about the error, making it easier to act upon or log effectively.

5. Building a Global Error Handling Strategy

As your application scales, you’ll want a centralized way to handle errors. One approach is to create an error-handling utility that handles different error types.

function handleError(error) {
  if (error instanceof ValidationError) {
    console.error("Validation failed:", error.message);
  } else if (error instanceof HttpError) {
    console.error(`HTTP error (${error.statusCode}):`, error.message);
  } else {
    console.error("An unexpected error occurred:", error);
  }
}

try {
  throw new HttpError(500, "Internal Server Error");
} catch (error) {
  handleError(error);
}
Enter fullscreen mode Exit fullscreen mode

This centralized error handling helps keep your code DRY (Don't Repeat Yourself) and ensures consistent error processing across your application.

6. Leveraging finally for Cleanup

Often, you might need to perform cleanup tasks like closing a database connection or clearing timeouts. That’s where finally comes in handy:

try {
  // Attempt to execute code
} catch (error) {
  // Handle errors
} finally {
  console.log("This will always execute, whether an error occurred or not.");
}
Enter fullscreen mode Exit fullscreen mode

Using finally ensures that this block executes regardless of whether an error was thrown or not, making it ideal for cleanup operations.

7. Integrating with Logging Services

For production applications, it’s essential to log errors. Services like Sentry, LogRocket, or Datadog can capture errors with full stack traces, making troubleshooting much easier.

Example:

import * as Sentry from '@sentry/browser';

Sentry.init({ dsn: 'YOUR_SENTRY_DSN' });

try {
  // Your code that might throw an error
} catch (error) {
  Sentry.captureException(error);
  handleError(error);
}
Enter fullscreen mode Exit fullscreen mode

This integration provides better visibility into issues and helps you monitor the health of your application in real-time.

Final Thoughts

By moving beyond the basics of try...catch and incorporating custom errors, you create more maintainable, readable, and robust JavaScript code. Implementing these practices will not only make debugging a breeze but also improve the overall reliability of your application.

What’s Next?

  • Experiment with custom errors in your current project.
  • Implement a centralized error handler.
  • Integrate error logging services in your app.

Let me know how you handle errors in your projects, or if you have any cool tips and tricks to share!


Enjoyed this post? Follow me for more JavaScript tips and tricks!

Top comments (0)