| title | Handle Errors with catchTag, catchTags, and catchAll | ||||
|---|---|---|---|---|---|
| id | handle-errors-with-catch | ||||
| skillLevel | intermediate | ||||
| applicationPatternId | error-management | ||||
| summary | Use catchTag for type-safe recovery from specific tagged errors, and catchAll to recover from any possible failure. | ||||
| tags |
|
||||
| rule |
|
||||
| related |
|
||||
| author | effect_website | ||||
| lessonOrder | 5 |
To recover from failures, use the catch* family of functions.
Effect.catchTag for specific tagged errors, Effect.catchTags for multiple,
and Effect.catchAll for any error.
Effect's structured error handling allows you to build resilient applications.
By using tagged errors and catchTag, you can handle different failure
scenarios with different logic in a type-safe way.
import { Data, Effect } from "effect";
// Define domain types
interface User {
readonly id: string;
readonly name: string;
}
// Define specific error types
class NetworkError extends Data.TaggedError("NetworkError")<{
readonly url: string;
readonly code: number;
}> {}
class ValidationError extends Data.TaggedError("ValidationError")<{
readonly field: string;
readonly message: string;
}> {}
class NotFoundError extends Data.TaggedError("NotFoundError")<{
readonly id: string;
}> {}
// Define UserService
class UserService extends Effect.Service<UserService>()("UserService", {
sync: () => ({
// Fetch user data
fetchUser: (
id: string
): Effect.Effect<User, NetworkError | NotFoundError> =>
Effect.gen(function* () {
yield* Effect.logInfo(`Fetching user with id: ${id}`);
if (id === "invalid") {
const url = "/api/users/" + id;
yield* Effect.logWarning(`Network error accessing: ${url}`);
return yield* Effect.fail(new NetworkError({ url, code: 500 }));
}
if (id === "missing") {
yield* Effect.logWarning(`User not found: ${id}`);
return yield* Effect.fail(new NotFoundError({ id }));
}
const user = { id, name: "John Doe" };
yield* Effect.logInfo(`Found user: ${JSON.stringify(user)}`);
return user;
}),
// Validate user data
validateUser: (user: User): Effect.Effect<string, ValidationError> =>
Effect.gen(function* () {
yield* Effect.logInfo(`Validating user: ${JSON.stringify(user)}`);
if (user.name.length < 3) {
yield* Effect.logWarning(
`Validation failed: name too short for user ${user.id}`
);
return yield* Effect.fail(
new ValidationError({ field: "name", message: "Name too short" })
);
}
const message = `User ${user.name} is valid`;
yield* Effect.logInfo(message);
return message;
}),
}),
}) {}
// Compose operations with error handling using catchTags
const processUser = (
userId: string
): Effect.Effect<string, never, UserService> =>
Effect.gen(function* () {
const userService = yield* UserService;
yield* Effect.logInfo(`=== Processing user ID: ${userId} ===`);
const result = yield* userService.fetchUser(userId).pipe(
Effect.flatMap(userService.validateUser),
// Handle different error types with specific recovery logic
Effect.catchTags({
NetworkError: (e) =>
Effect.gen(function* () {
const message = `Network error: ${e.code} for ${e.url}`;
yield* Effect.logError(message);
return message;
}),
NotFoundError: (e) =>
Effect.gen(function* () {
const message = `User ${e.id} not found`;
yield* Effect.logWarning(message);
return message;
}),
ValidationError: (e) =>
Effect.gen(function* () {
const message = `Invalid ${e.field}: ${e.message}`;
yield* Effect.logWarning(message);
return message;
}),
})
);
yield* Effect.logInfo(`Result: ${result}`);
return result;
});
// Test with different scenarios
const runTests = Effect.gen(function* () {
yield* Effect.logInfo("=== Starting User Processing Tests ===");
const testCases = ["valid", "invalid", "missing"];
const results = yield* Effect.forEach(testCases, (id) => processUser(id));
yield* Effect.logInfo("=== User Processing Tests Complete ===");
return results;
});
// Run the program
Effect.runPromise(Effect.provide(runTests, UserService.Default));Explanation:
Use catchTag to handle specific error types in a type-safe, composable way.
Using try/catch blocks inside your Effect compositions. It breaks the
declarative flow and bypasses Effect's powerful, type-safe error channels.