TypeScript Error Handling
Error handling is an important part of ensuring program robustness.
TypeScript provides a powerful type system that helps developers better handle and prevent errors.
This article introduces common error handling patterns and best practices in TypeScript.
Why Good Error Handling Is Needed
Any program may encounter error situations such as network request failures, missing files, or incorrect user input.
Good error handling can prevent program crashes, provide friendly error messages, and help developers locate issues.
TypeScript's type system can detect potential problems at compile time, reducing runtime errors.
Concept Explanation: There are two main approaches to error handling: exception handling (try-catch) and return value handling (Result type). The former uses throwing exceptions to indicate errors, while the latter uses return values to carry error information.
Custom Error Types
Creating custom error types by extending the Error class allows carrying more error information.
This makes error handling more precise and structured.
Example
// Define application error class
// Extend built-in Error class, add error code
class AppError extends Error {
// Error code for programmatic error handling
code: string;
// Constructor
constructor(message: string, code: string){
super(message); // Call parent constructor
this.name = "AppError"; // Set error name
this.code = code; // Save error code
}
}
// Safe division function
function divide(a: number, b: number): number {
// Check if divisor is zero
if(b === 0){
// Throw custom error
throw new AppError("Cannot divide by zero", "DIVIDE_BY_ZERO");
}
return a / b;
}
// Use try-catch to catch errors
try{
var result = divide(10, 0);
}catch(error){
// Check error type
if(error instanceof AppError){
console.log("Application error: "+ error.message+", code: "+ error.code);
}else{
console.log("Unknown error: "+ error);
}
}
Output:
Application error: Cannot divide by zero, code: DIVIDE_BY_ZERO
Error Codes: Adding codes to errors helps programs handle different types of errors more precisely.
Using Result Type to Avoid Exceptions
Another approach to error handling is using the Result type.
It carries error information through return values instead of throwing exceptions. This approach is common in functional programming.
Example
// Define Result type using union types
// Success case includes ok: true and value, failure case includes ok: false and error
type Result =
| { ok: true; value: T }
| { ok: false; error: E };
// Division function using Result type
function safeDivide(a: number, b: number): Result{
// Check if divisor is zero
if(b === 0){
// Return error result
return { ok: false, error: "Cannot divide by zero" };
}
// Return success result
return { ok: true, value: a / b };
}
// Call function and process result
var result = safeDivide(10, 2);
// Process based on result type
if(result.ok){
console.log("Result: "+ result.value);
}else{
console.log("Error: "+ result.error);
}
Output:
Result: 5
Advantages: The Result type makes error handling explicit. Callers must handle possible errors instead of ignoring them.
Async Function Error Handling
In asynchronous functions, error handling is especially important.
You can use try-catch or Result types to handle errors in asynchronous operations.
Example
// Define user interface
interface User {
id: number;
name: string;
}
// Simulate async function to fetch user
async function fetchUser(id: number): Promise<Result>{
try{
// Simulate network request
var response = await fetch("/api/users/"+ id);
var user = await response.json();
// Return success result
return { ok: true, value: user };
}catch(error){
// Return error result
return { ok: false, error: error as Error };
}
}
// Main function
async function main(){
// Call async function
var result = await fetchUser(1);
// Process result
if(result.ok){
console.log("User: "+ JSON.stringify(result.value));
}else{
console.log("Error: "+ result.error.message);
}
}
// Execute main function
main();
Output:
User: {"id":1,"name":"Alice"}
Tip: In async functions, try-catch catches any errors thrown by await expressions.
General Error Handling Encapsulation
You can create a general error handling function to simplify error handling in asynchronous code.
Example
// General error handling wrapper function
// Accepts an async function and returns a Result type
async function withErrorHandling(
fn: () => Promise
): Promise<Result>{
try{
// Execute the passed async function
var data = await fn();
// Return success result
return { ok: true, value: data };
}catch(error){
// Return error result
return { ok: false, error: error as Error };
}
}
// Use general error handling
// Simulate fetching data
var result = await withErrorHandling(async function(){
var response = await fetch("/api/data");
return response.json();
});
// Process based on result
if(result.ok){
console.log("Data: "+ JSON.stringify(result.value));
}else{
console.error("Error:", result.error);
}
Best Practice: Encapsulating general error handling logic reduces code duplication and improves maintainability.
Considerations
- Don't ignore errors: Don't use empty catch blocks to catch and ignore errors
- Specify error types clearly: Prefer specific error types over generic Error
- Error boundaries: Establish unified error handling mechanisms in your application
- Avoid overusing exceptions: For predictable errors, prefer return values over throwing exceptions
Suggestion: Choose error handling methods based on context: use exceptions for program errors, Result for business errors.
Summary
Good error handling is the foundation of building robust applications.
- Custom Errors: Extend Error class, add error codes and other information
- Result Type: Handle errors via return values, avoid exceptions
- async/await: Use try-catch to handle asynchronous errors
- Error Encapsulation: Create general error handling functions
- Error Boundaries: Establish unified error handling mechanisms
Best Practice: Choose appropriate error handling methods based on specific scenarios, balancing code readability and robustness.
YouTip