Effect
The Effect module is the core of the Effect library, providing a powerful way to model and compose asynchronous, concurrent, and effectful computations.
An Effect<A, E, R> represents a computation that:
- May succeed with a value of type
A
- May fail with an error of type
E
- Requires a context/environment of type
R
Key Features
- Type-safe error handling: Errors are tracked in the type system
- Resource management: Automatic cleanup with scoped resources
- Structured concurrency: Safe parallel and concurrent execution
- Composable: Effects can be combined using operators like
flatMap, map, zip
- Testable: Built-in support for testing with controlled environments
- Interruptible: Effects can be safely interrupted and cancelled
Type Signature
interface Effect<out A, out E = never, out R = never>
The success type - the value type that the effect will produce when it succeeds
E
type parameter
default:"never"
The error type - the type of error that the effect may fail with
R
type parameter
default:"never"
The context/environment type - the services required to run the effect
Creating Effects
succeed
Creates an Effect that always succeeds with a given value.
const success: Effect<number> = Effect.succeed(42)
Signature:
function succeed<A>(value: A): Effect<A>
The value to succeed with
An effect that succeeds with the provided value
fail
Creates an Effect that represents a failure.
class MyError extends Data.TaggedError("MyError")<{}> {}
const failure: Effect<never, MyError> = Effect.fail(new MyError())
Signature:
function fail<E>(error: E): Effect<never, E>
The error value to fail with
An effect that fails with the provided error
sync
Creates an Effect from a synchronous side-effectful function.
const getTimestamp = Effect.sync(() => Date.now())
Signature:
function sync<A>(evaluate: () => A): Effect<A>
A function that will be evaluated when the effect runs
An effect that evaluates the function when run
promise
Creates an Effect that represents an asynchronous computation guaranteed to succeed.
const delay = (message: string) =>
Effect.promise<string>(() =>
new Promise((resolve) => {
setTimeout(() => resolve(message), 2000)
})
)
Signature:
function promise<A>(
evaluate: (signal: AbortSignal) => PromiseLike<A>
): Effect<A>
evaluate
(signal: AbortSignal) => PromiseLike<A>
required
A function that returns a Promise. Receives an AbortSignal for interruption support.
An effect that wraps the promise computation
tryPromise
Creates an Effect that represents an asynchronous computation that might fail.
class FetchError extends Data.TaggedError("FetchError")<{ cause: unknown }> {}
const getTodo = (id: number) =>
Effect.tryPromise({
try: () => fetch(`https://api.example.com/todos/${id}`),
catch: (cause) => new FetchError({ cause })
})
Signature:
function tryPromise<A, E>(
options:
| { try: (signal: AbortSignal) => PromiseLike<A>; catch: (error: unknown) => E }
| ((signal: AbortSignal) => PromiseLike<A>)
): Effect<A, E>
options.try
(signal: AbortSignal) => PromiseLike<A>
required
A function that returns a Promise that may reject
Optional function to map rejection errors. If omitted, errors become UnknownError.
An effect that may fail with the mapped error type
suspend
Delays the creation of an Effect until it is actually needed.
let i = 0
const good = Effect.suspend(() => Effect.succeed(i++))
console.log(Effect.runSync(good)) // 0
console.log(Effect.runSync(good)) // 1
Signature:
function suspend<A, E, R>(effect: () => Effect<A, E, R>): Effect<A, E, R>
effect
() => Effect<A, E, R>
required
A thunk that returns an Effect. Evaluated lazily each time the effect runs.
A suspended effect that re-evaluates on each run
map
Transforms the success value of an effect.
const doubled = Effect.succeed(21).pipe(
Effect.map((n) => n * 2)
)
// Effect<number> that succeeds with 42
Signature:
function map<A, B>(f: (a: A) => B): <E, R>(self: Effect<A, E, R>) => Effect<B, E, R>
function map<A, E, R, B>(self: Effect<A, E, R>, f: (a: A) => B): Effect<B, E, R>
flatMap
Chains effects to produce new Effect instances, useful for combining operations that depend on previous results.
const program = Effect.succeed(10).pipe(
Effect.flatMap((n) => Effect.succeed(n * 2))
)
// Effect<number> that succeeds with 20
Signature:
function flatMap<A, B, E1, R1>(
f: (a: A) => Effect<B, E1, R1>
): <E, R>(self: Effect<A, E, R>) => Effect<B, E1 | E, R1 | R>
andThen
Chains two actions, where the second action can depend on the result of the first.
const result = Effect.succeed(5).pipe(
Effect.andThen((n) => Effect.succeed(n * 2)),
Effect.andThen((n) => Effect.succeed(n + 10))
)
// Effect<number> that succeeds with 20
Signature:
function andThen<A, B, E2, R2>(
f: (a: A) => Effect<B, E2, R2>
): <E, R>(self: Effect<A, E, R>) => Effect<B, E | E2, R | R2>
Error Handling
catchAll
Handles all errors in an effect by providing a recovery effect.
const program = Effect.fail("error").pipe(
Effect.catchAll((error) => Effect.succeed(`Recovered from: ${error}`))
)
Signature:
function catchAll<E, A2, E2, R2>(
f: (e: E) => Effect<A2, E2, R2>
): <A, R>(self: Effect<A, E, R>) => Effect<A | A2, E2, R | R2>
orElse
Tries an effect, and if it fails, tries another effect.
const fallback = Effect.fail("primary error").pipe(
Effect.orElse(() => Effect.succeed("fallback value"))
)
Signature:
function orElse<A2, E2, R2>(
that: () => Effect<A2, E2, R2>
): <A, E, R>(self: Effect<A, E, R>) => Effect<A | A2, E2, R | R2>
Running Effects
runPromise
Executes an effect and returns a Promise of the result.
const program = Effect.succeed(42)
Effect.runPromise(program).then(console.log) // 42
Signature:
function runPromise<A, E>(effect: Effect<A, E>): Promise<A>
runSync
Executes an effect synchronously and returns the result.
const result = Effect.runSync(Effect.succeed(42)) // 42
Signature:
function runSync<A, E>(effect: Effect<A, E>): A
runSync will throw if the effect is asynchronous or fails. Use runPromise for async effects.
Generator Functions
gen
Provides a generator-based syntax for writing effectful code.
const program = Effect.gen(function*() {
const a = yield* Effect.succeed(10)
const b = yield* Effect.succeed(20)
return a + b
})
// Effect<number> that succeeds with 30
Signature:
function gen<Eff extends YieldableError, AEff>(
f: (resume: Resume) => Generator<Eff, AEff, never>
): Effect<AEff, Effect.Error<Eff>, Effect.Services<Eff>>
Collecting Effects
all
Runs multiple effects and collects their results.
const program = Effect.all([
Effect.succeed(1),
Effect.succeed(2),
Effect.succeed(3)
])
// Effect<[number, number, number]>
Signature:
function all<const T extends Iterable<Effect<any, any, any>>>(
effects: T,
options?: { concurrency?: Concurrency; discard?: boolean }
): Effect</* collected results */, /* union of errors */, /* union of contexts */>
Collection of effects to run
Controls how effects run: "sequential", "unbounded", or a number
If true, discards results and returns void
Type Utilities
Success
Extracts the success type from an Effect.
type MyEffect = Effect<string, Error, never>
type SuccessType = Effect.Success<MyEffect> // string
Error
Extracts the error type from an Effect.
type MyEffect = Effect<string, Error, never>
type ErrorType = Effect.Error<MyEffect> // Error
Services
Extracts the context/services type from an Effect.
type MyEffect = Effect<string, Error, { db: Database }>
type ServicesType = Effect.Services<MyEffect> // { db: Database }
Example Usage
import { Console, Effect, Data } from "effect"
class ValidationError extends Data.TaggedError("ValidationError")<{
message: string
}> {}
// Creating effects
const validateAge = (age: number) =>
age >= 18
? Effect.succeed(age)
: Effect.fail(new ValidationError({ message: "Must be 18 or older" }))
// Composing effects
const program = Effect.gen(function*() {
const age = yield* Effect.succeed(25)
const validated = yield* validateAge(age)
yield* Console.log(`Valid age: ${validated}`)
return validated
})
// Running the effect
Effect.runPromise(program)
.then(result => console.log(`Result: ${result}`))
.catch(error => console.error(`Error: ${error}`))