Skip to content

Using Generators

Effect.gen lets you write effectful code in a familiar, imperative style - the same shape as async/await, but for effects. You pass it a generator function, and inside it you use yield* to run an effect and bind its success value to a variable. Errors and service requirements are threaded through the types for you, so the result is a single Effect describing the whole sequence.

import { Effect, Schema } from "effect"
// Use Schema.TaggedErrorClass to define a typed, tagged error.
class FileProcessingError extends Schema.TaggedErrorClass<FileProcessingError>()(
"FileProcessingError",
{ message: Schema.String }
) {}
const program = Effect.gen(function*() {
yield* Effect.log("Starting the file processing...")
yield* Effect.log("Reading file...")
// Raising an error short-circuits the generator. Always `return` the
// yielded error so TypeScript knows execution stops here and narrows the
// types of anything after it.
return yield* new FileProcessingError({ message: "Failed to read the file" })
}).pipe(
// Attach additional behaviour with `.pipe` after the generator.
Effect.catch((error) => Effect.logError(`An error occurred: ${error}`)),
Effect.withSpan("fileProcessing")
)
Effect.runFork(program)

yield* is the heart of the syntax. Yielding an Effect<A, E, R>:

  • runs that effect at this point in the sequence,
  • binds its success value A to the left-hand side,
  • and accumulates its error type E and requirements R into the overall effect that Effect.gen returns.
import { Effect } from "effect"
const divide = (a: number, b: number) =>
b === 0
? Effect.fail("Cannot divide by zero" as const)
: Effect.succeed(a / b)
// ┌─── Effect<number, "Cannot divide by zero">
// ▼
const program = Effect.gen(function*() {
// Each yield* gives back the success value of the effect.
const a = yield* Effect.succeed(10)
const b = yield* Effect.succeed(2)
// If `divide` fails, the generator stops and the error becomes the
// failure of the whole program - the next line never runs.
const result = yield* divide(a, b)
yield* Effect.log(`10 / 2 = ${result}`)
return result
})

Because yield* returns the plain success value, you can use ordinary JavaScript in between: const/let, arithmetic, if, early return. You do not need any special operators for non-effectful code.

A generator runs top to bottom until something fails. The first failure stops execution and becomes the failure of the entire effect - just like throw unwinds an async function, but tracked in the type system.

import { Effect } from "effect"
const program = Effect.gen(function*() {
yield* Effect.log("step 1")
yield* Effect.fail("boom" as const) // execution stops here
yield* Effect.log("step 2") // never runs
})

Always return yield* when yielding an effect that you intend as the failure path (like a constructed error). The return tells TypeScript control will not continue, which keeps later code correctly typed.

Some data types are also yieldable inside a generator. For example, an Option yields its inner value when it is Some, and short-circuits with a NoSuchElementError when it is None:

import { Effect, Option } from "effect"
const lookup = (key: string): Option.Option<number> =>
key === "answer" ? Option.some(42) : Option.none()
const program = Effect.gen(function*() {
// Yielding a `Some` produces its value...
const x = yield* lookup("answer")
// ...while a `None` fails the effect with NoSuchElementError.
const y = yield* lookup("missing")
return x + y
})

If you need access to the surrounding object (for example inside a class method), pass { self: this } as the first argument and the generator receives it as this:

import { Effect } from "effect"
class Calculator {
base = 100
total = Effect.gen({ self: this }, function*() {
// `this` refers to the Calculator instance.
const extra = yield* Effect.succeed(23)
return this.base + extra
})
}

Use Effect.gen whenever a computation has multiple sequential steps, local variables, or branching - it is the most readable way to express that. For a function that returns an effect, prefer Effect.fn over a plain function that returns Effect.gen(...). For short transformations and attaching cross-cutting behaviour, pipelines are often cleaner.