Result & Exit
So far errors have flowed through the error channel. Sometimes you want the opposite: to capture the outcome as an ordinary value you can pattern-match on. Effect offers two such types.
Result<A, E>is a synchronous, two-case value:Success<A>orFailure<E>. It is the v4 replacement forEither, and models a computation that either produced a value or a typed error — nothing more.Exit<A, E>is the outcome of running an effect:Success<A>or aFailurecarrying a fullCause<E>. Because a fiber can fail with typed errors, defects, and interruptions at once, the failure side is aCause, not a bareE.
import { Effect, Result } from "effect"
// `Effect.result` moves the error into the success channel as a Result,// so the effect itself can no longer fail.// ┌─── Effect<Result<number, string>, never>// ▼const program = Effect.fail("boom").pipe( Effect.as(42), Effect.result)
const handled = program.pipe( Effect.map((result) => Result.match(result, { onSuccess: (value) => `ok: ${value}`, onFailure: (error) => `error: ${error}` }) ))Result — success or typed error as a value
Section titled “Result — success or typed error as a value”A Result is a discriminated union with _tag of "Success" or "Failure".
Access the payload through .success or .failure after narrowing, or fold it
with Result.match.
import { Result } from "effect"
const ok = Result.succeed(42) // Result<number, never>const err = Result.fail("nope") // Result<never, string>
// Narrow with the type guards...if (Result.isSuccess(ok)) { console.log(ok.success) // 42}if (Result.isFailure(err)) { console.log(err.failure) // "nope"}
// ...or fold both cases at once.const message = Result.match(err, { onSuccess: (value) => `value ${value}`, onFailure: (error) => `failed: ${error}`})Use Effect.result to turn an Effect<A, E> into an Effect<Result<A, E>> —
handy when you want to inspect the outcome inside a gen block without
short-circuiting on failure.
import { Effect, Result } from "effect"
declare const fetchUser: (id: number) => Effect.Effect<string, Error>
const program = Effect.gen(function* () { // `yield* Effect.result(...)` never throws — it yields a Result. const result = yield* Effect.result(fetchUser(1)) if (Result.isFailure(result)) { yield* Effect.log(`recovering from ${result.failure.message}`) return "default" } return result.success})Exit — the full outcome of a run
Section titled “Exit — the full outcome of a run”An Exit is what you get when you run an effect to completion. The runtime
returns one from Effect.runSyncExit / Effect.runPromiseExit, and you can
capture one mid-program with Effect.exit.
import { Effect, Exit } from "effect"
const program = Effect.succeed(42)
const exit = Effect.runSyncExit(program)
const summary = Exit.match(exit, { onSuccess: (value) => `produced ${value}`, // The failure side receives a Cause, not a bare error. onFailure: (cause) => `failed with ${cause}`})The difference from Result is the failure side: a Result.Failure holds a
typed E, while an Exit.Failure holds a Cause<E>. That’s because running a
fiber can fail in ways the type channel doesn’t capture — defects and
interruptions — and Exit keeps all of them.
Cause — the full story
Section titled “Cause — the full story”A Cause<E> is the runtime’s complete record of why a fiber stopped. It is a
collection of reasons, each of which is one of:
Fail— a typed, expected error (theE), accessed viareason.error.Die— an untyped defect, accessed viareason.defect.Interrupt— a fiber interruption, carrying the interruptingfiberId.
import { Cause, Effect, Exit } from "effect"
const program = Effect.die(new Error("kaboom"))
const exit = Effect.runSyncExit(program)
if (Exit.isFailure(exit)) { const cause = exit.cause
// Ask high-level questions about the cause... console.log(Cause.hasDies(cause)) // true console.log(Cause.hasFails(cause)) // false
// ...or walk its reasons directly. for (const reason of cause.reasons) { if (Cause.isDieReason(reason)) { console.log("defect:", reason.defect) } else if (Cause.isFailReason(reason)) { console.log("error:", reason.error) } }
// `Cause.pretty` renders a human-readable report for logs. console.log(Cause.pretty(cause))}When to use which
Section titled “When to use which”- Use
Resultfor synchronous success-or-error values, and to inspect an effect’s typed outcome without failing the surrounding effect. - Use
Exitwhen you run an effect and need to know exactly how it ended, including defects and interruptions. - Reach into
Causewhenever you need to tell those failure kinds apart.
Next steps
Section titled “Next steps”- See
Causein handlers viacatchCauseandmatchCause. - Learn how
Resultplays into validation in Schema.