Catching Errors
Once an error is in the E channel, you recover from it with one of the catch
combinators. Each handler returns a new effect, and — crucially — handling an
error removes it from the error type, so the compiler always knows which
failures are still outstanding.
import { Effect, Schema } from "effect"
class ParseError extends Schema.TaggedErrorClass<ParseError>()("ParseError", { input: Schema.String}) {}
class ReservedPortError extends Schema.TaggedErrorClass<ReservedPortError>()( "ReservedPortError", { port: Schema.Number }) {}
declare const loadPort: ( input: string) => Effect.Effect<number, ParseError | ReservedPortError>
// Catch BOTH tagged errors with a single `catchTag` call, returning a default.// ┌─── Effect<number, never> — every error handled// ▼const recovered = loadPort("80").pipe( Effect.catchTag(["ParseError", "ReservedPortError"], () => Effect.succeed(3000)))catchTag — recover from one tag
Section titled “catchTag — recover from one tag”Effect.catchTag matches a tagged error by its _tag. The handler receives the
narrowed error, so its fields are available, and the matched tag disappears from
the resulting error channel.
import { Effect, Schema } from "effect"
class ValidationError extends Schema.TaggedErrorClass<ValidationError>()( "ValidationError", { message: Schema.String }) {}
class NetworkError extends Schema.TaggedErrorClass<NetworkError>()( "NetworkError", { statusCode: Schema.Number }) {}
declare const fetchUser: ( id: string) => Effect.Effect<string, ValidationError | NetworkError>
const program = fetchUser("123").pipe( // Handle only ValidationError; NetworkError still flows through. Effect.catchTag("ValidationError", (error) => Effect.succeed(`invalid: ${error.message}`) ))// program: Effect<string, NetworkError>Pass an array of tags to handle several with the same handler, as in the opening example.
catchTags — a handler per tag
Section titled “catchTags — a handler per tag”When you want a different recovery for each error, Effect.catchTags takes an
object keyed by tag. Each handler again receives its narrowed error.
import { Effect, Schema } from "effect"
class ValidationError extends Schema.TaggedErrorClass<ValidationError>()( "ValidationError", { message: Schema.String }) {}
class NetworkError extends Schema.TaggedErrorClass<NetworkError>()( "NetworkError", { statusCode: Schema.Number }) {}
declare const fetchUser: ( id: string) => Effect.Effect<string, ValidationError | NetworkError>
const userOrFallback = fetchUser("123").pipe( Effect.catchTags({ ValidationError: (error) => Effect.succeed(`Validation failed: ${error.message}`), NetworkError: (error) => Effect.succeed(`Network request failed with status ${error.statusCode}`) }))// userOrFallback: Effect<string, never>catch — recover from any failure
Section titled “catch — recover from any failure”Effect.catch is the catch-all for typed failures. The handler receives the
whole E union, and the error channel collapses to whatever the handler can
still fail with.
import { Effect, Schema } from "effect"
class ParseError extends Schema.TaggedErrorClass<ParseError>()("ParseError", { input: Schema.String}) {}
class ReservedPortError extends Schema.TaggedErrorClass<ReservedPortError>()( "ReservedPortError", { port: Schema.Number }) {}
declare const loadPort: ( input: string) => Effect.Effect<number, ParseError | ReservedPortError>
const withFinalFallback = loadPort("invalid").pipe( // Handle one specific error first... Effect.catchTag("ReservedPortError", () => Effect.succeed(3000)), // ...then mop up anything still failing with a catch-all. Effect.catch(() => Effect.succeed(3000)))catchIf — recover when a predicate matches
Section titled “catchIf — recover when a predicate matches”When the discriminator isn’t a _tag, Effect.catchIf recovers based on a
predicate or refinement.
import { Effect } from "effect"
declare const request: Effect.Effect<string, { status: number }>
const program = request.pipe( Effect.catchIf( (error) => error.status === 404, () => Effect.succeed("not found, using default") ))catchCause — recover from defects too
Section titled “catchCause — recover from defects too”Everything above ignores defects and interruptions by design. When you genuinely
need the full picture — to recover a plugin that threw, or to log a defect —
reach for Effect.catchCause. The handler receives the entire
Cause.
import { Cause, Effect } from "effect"
// `Effect.die` records a defect, so `catch`/`catchTag` would NOT see it.const program = Effect.die(new Error("boom"))
const recovered = program.pipe( Effect.catchCause((cause) => // Render the cause (failures + defects + interruptions) for logging. Effect.succeed(`recovered from: ${Cause.pretty(cause)}`) ))There is also Effect.catchDefect, which recovers from defects specifically
while leaving typed failures in place — handy when you only want to contain
unexpected errors.
Catching and re-failing
Section titled “Catching and re-failing”A handler is just an effect, so it can transform an error instead of fully
recovering. Return a new Effect.fail to map one error to another, or
Effect.die to promote it to a defect.
import { Effect, Schema } from "effect"
class LowLevelError extends Schema.TaggedErrorClass<LowLevelError>()( "LowLevelError", { detail: Schema.String }) {}
class DomainError extends Schema.TaggedErrorClass<DomainError>()("DomainError", { message: Schema.String}) {}
declare const lowLevel: Effect.Effect<number, LowLevelError>
const mapped = lowLevel.pipe( // Translate an infrastructure error into a domain error. Effect.catchTag("LowLevelError", (error) => Effect.fail(new DomainError({ message: `wrapped: ${error.detail}` })) ))// mapped: Effect<number, DomainError>Next steps
Section titled “Next steps”- Handle a fixed set of sub-causes inside one error with Reason Errors.
- Retry instead of recover with Fallback & Retry.
- Fold success and failure together with Matching.