Error Handling and Custom Errors
Workplace Context
As your team continues to develop the e-commerce platform, they want to ensure that any unexpected issues, such as invalid data inputs or connection failures, are handled gracefully. Poor error handling can lead to application crashes, loss of data, and a frustrating user experience. In large projects, well-structured error handling is crucial for maintaining reliability and providing helpful feedback when things go wrong.
This lesson will guide you through standard error-handling techniques in JavaScript and TypeScript, with a focus on best practices and custom error handling in larger projects.
Learning Objectives
By the end of this lesson, you will be able to:
- Use
try/catch/finally
blocks to handle errors in JavaScript. - Implement custom error classes in TypeScript to manage specific types of errors.
- Apply best practices for error handling in JavaScript and TypeScript projects.
Introduction to Error Handling
Error handling is the process of managing unexpected conditions that can disrupt the normal flow of a program. These conditions are often called exceptions, and they can be caused by things like invalid user input, missing files, or server connection failures.
Without proper error handling, an error could cause the application to stop executing entirely, leading to crashes or data loss. JavaScript provides built-in mechanisms to manage errors gracefully.
try
, catch
, and finally
JavaScript uses the try
, catch
, and finally
keywords to handle exceptions:
try
: Wraps the code that might throw an error.catch
: Handles the error if it occurs.finally
: Executes code aftertry
/catch
, regardless of whether an error was thrown.
Here is the basic syntax:
try {
// Code that may throw an error
} catch (error) {
// Handle the error
} finally {
// Code that will always execute
}
Example: Basic Error Handling
Explanation
- If
b
is 0, thedivideNumbers
function throws an error. - The
catch
block catches the error and displays a message. - The
finally
block executes regardless of whether an error occurred, useful for cleanup operations (e.g., closing database connections).
Creating Custom Errors
In JavaScript and TypeScript, you can create custom error classes that extend the built-in Error
class. This makes it easier to handle specific types of errors separately and provide more detailed feedback.
Think of custom errors as specialized tools in a toolbox. You don’t always need them, but they are essential for unique tasks. Custom errors allow you to differentiate between various error conditions and handle them accordingly.
Example: Custom Error Class
Here is an example of a custom error class in TypeScript:
An Error in the Error Example?
You may notice that this code does not behave as we expect it to, by logging “Unknown Error” instead of “Validation Error”.
This is due to a bug in the code bundler that is being used to power these interactive editors! This is an important distinction to make between bugs and errors: sometimes, bugs do not throw errors, which makes them more difficult to track down and address. This is where testing comes in!
Copy the code from the example above, and run it using your own environment or within the Chrome DevTools (you will need to remove the type annotations to translate the code into valid JavaScript), and it should work properly. You will see the following output:
Validation Error: Username must be at least 5 characters long.
Explanation
- Custom Error Class:
ValidationError
extends the built-inError
class. - Error Checking: The
catch
block usesinstanceof
to check if the error is aValidationError
. - This makes it easier to handle different types of errors separately.
Error Handling Best Practices
Proper error handling is crucial for building reliable applications. Here are some best practices to follow:
1. Be Specific with Errors
Use specific error messages to make debugging easier. Avoid vague error messages like "Something went wrong."
Instead, provide meaningful information:
throw new Error("Cannot connect to the server. Please check your network.");
2. Use Custom Error Classes
Custom error classes make it easier to manage and handle specific error types in large codebases. This allows you to differentiate between critical and non-critical errors.
3. Avoid Swallowing Errors
Do not use empty catch
blocks, as this makes it difficult to identify and fix problems.
// Bad Practice
try {
riskyOperation();
} catch (error) {
// Empty catch block - error is ignored
}
// Good Practice
try {
riskyOperation();
} catch (error) {
console.error("An error occurred:", error.message);
}
4. Log Errors for Debugging
Always log errors for debugging purposes, especially in larger projects. Use tools like console.error
, logging libraries (e.g., Winston
), or external monitoring services.
5. Use finally
for Cleanup
Use the finally
block for cleanup tasks, such as closing files or releasing resources, regardless of whether an error occurred.
6. Avoid Throwing Strings as Errors
Always use the Error
class or custom error classes instead of throwing strings. This provides a consistent error structure and better debugging information.
// Bad Practice
throw "An unexpected error occurred.";
// Good Practice
throw new Error("An unexpected error occurred with data validation; see server logs for more details.");
7. Graceful Error Handling in Production
Avoid displaying raw error messages to end-users in production environments. Instead, provide friendly feedback and log the errors for developers. Use environment checks to display detailed error information only in development:
if (process.env.NODE_ENV === "development") {
console.error(error);
} else {
console.log("An unexpected issue occurred. Please try again later.");
}
Error Handling in TypeScript
TypeScript provides static typing, which reduces the likelihood of some common errors. However, runtime errors can still occur, and proper error handling is essential. TypeScript can enforce better error-handling practices with tools like:
- Type Guards: Ensure variables are of a specific type before accessing their properties.
- Discriminated Unions: Use union types with discriminating properties to enforce correct behavior.
Example: Type Guard for Error Handling
Here is an example using a type guard to handle potential errors in TypeScript:
Explanation:
- The
isError
function is a type guard that checks if the response is an error. - This example uses a discriminated union to handle both successful and error responses gracefully.
Activity: Implement Custom Error Handling
Time: 45 minutes
Instructions:
- Create a file called
customErrorHandling.ts
. - Write a function called
processOrder
that:- Takes an order object with properties:
productId
,quantity
, andprice
. - Throws a
ValidationError
ifquantity
is less than 1. - Throws a
PaymentError
ifprice
is not a positive number.
- Takes an order object with properties:
- Create two custom error classes:
ValidationError
andPaymentError
. - Implement a function called
handleOrder
that catches and logs these custom errors.
Critical Thinking: How do custom errors help in identifying specific issues in larger codebases? What challenges might arise if you only use generic error messages?
Knowledge Check
What is the purpose of the 'finally' block in error handling?
- Select an answer to view feedback.
What is the primary benefit of using custom error classes in JavaScript/TypeScript?
- Select an answer to view feedback.
Which practice should you avoid when handling errors in JavaScript?
- Select an answer to view feedback.
What does a type guard
do in TypeScript error handling?
- Select an answer to view feedback.
Summary
In this lesson, you learned about handling errors effectively in JavaScript and TypeScript. You explored how to use try
, catch
, and finally
blocks to manage exceptions, create custom error classes for specific scenarios, and follow best practices for reliable error handling. These techniques are vital for building robust, maintainable applications that can handle unexpected scenarios gracefully.