Effect.gen Quickstart

Basic Generator

import { Effect, Console } from "effect"

const program = Effect.gen(function*() {
  yield* Console.log("Starting...")
  const a = yield* Effect.succeed(10)
  const b = yield* Effect.succeed(20)
  return a + b
})

Effect.runPromise(program).then(console.log)
// Output:
// Starting...
// 30

Each yield* runs an effect and binds its success value. If any effect fails, execution stops there. This reads like async/await but with synchronous-looking control flow and full type inference.

Error Handling with return yield*

import { Effect, Schema, Console } from "effect"

class ValidationError extends Schema.TaggedError<ValidationError>()("ValidationError", {
  message: Schema.String
}) {}

const validate = (age: number) =>
  Effect.gen(function*() {
    if (age < 0) {
      return yield* new ValidationError({ message: "Age cannot be negative" })
    }
    if (age > 150) {
      return yield* new ValidationError({ message: "Age seems unrealistic" })
    }
    return age
  })

const program = Effect.gen(function*() {
  const result = yield* validate(200).pipe(
    Effect.catchTag("ValidationError", (err) =>
      Effect.gen(function*() {
        yield* Console.log(`Caught: ${err.message}`)
        return 0
      })
    )
  )
  return result
})

Effect.runPromise(program).then(console.log)
// Output:
// Caught: Age seems unrealistic
// 0

Gotcha: Always use return yield* when yielding a failing effect. Without return, TypeScript won’t narrow the type, and the generator continues with unreachable code after the error.

Logging

import { Effect, Console } from "effect"

const program = Effect.gen(function*() {
  yield* Effect.logInfo("Fetching data...")
  yield* Effect.logDebug("Debug details here")
  const data = yield* Effect.succeed({ id: 1, name: "Alice" })
  yield* Console.log(`Got user: ${data.name}`)
  return data
})

Effect.runPromise(program)

Effect has structured logging with levels: logInfo, logDebug, logWarning, logError, and log. Logs are not printed to stdout by default in v4. Use Console.log for direct stdout output, or configure a logger layer.

Effect.fn for Named Functions

import { Effect, Schema } from "effect"

class FetchError extends Schema.TaggedError<FetchError>()("FetchError", {
  url: Schema.String
}) {}

export const fetchJson = Effect.fn("fetchJson")(
  function*(url: string) {
    yield* Effect.logInfo(`Fetching ${url}`)
    return yield* Effect.tryPromise({
      try: () => fetch(url).then((r) => r.json()),
      catch: () => new FetchError({ url })
    })
  },
  Effect.catchTag("FetchError", (err) =>
    Effect.gen(function*() {
      yield* Effect.logWarning(`Retrying failed: ${err.url}`)
      return null
    })
  )
)

// Usage
const program = Effect.gen(function*() {
  const data = yield* fetchJson("https://api.example.com/users")
  return data
})

Effect.runPromise(program)

Effect.fn wraps a generator function so it returns an Effect. Pass a name string to get better stack traces and automatic tracing spans. Additional arguments after the function are combinators applied to every call, so you don’t need .pipe.

Tip: Don’t use .pipe on Effect.fn results. Pass combinators as extra arguments to Effect.fn instead.

A Practical Example

import { Effect, Schema, Console } from "effect"

class HttpError extends Schema.TaggedError<HttpError>()("HttpError", {
  status: Schema.Number,
  url: Schema.String
}) {}

class ParseError extends Schema.TaggedError<ParseError>()("ParseError", {
  message: Schema.String
}) {}

const fetchUser = Effect.fn("fetchUser")(
  function*(id: number) {
    yield* Effect.logInfo(`Fetching user ${id}`)

    const response = yield* Effect.tryPromise({
      try: () => fetch(`https://jsonplaceholder.typicode.com/users/${id}`),
      catch: () => new HttpError({ status: 0, url: `users/${id}` })
    })

    if (!response.ok) {
      return yield* new HttpError({ status: response.status, url: `users/${id}` })
    }

    const json = yield* Effect.tryPromise({
      try: () => response.json(),
      catch: () => new ParseError({ message: `Failed to parse response for user ${id}` })
    })

    yield* Effect.logInfo(`Successfully fetched user ${id}`)
    return json
  }
)

const program = Effect.gen(function*() {
  const user = yield* fetchUser(1).pipe(
    Effect.catchTag("HttpError", (err) =>
      Effect.gen(function*() {
        yield* Console.log(`HTTP ${err.status} on ${err.url}`)
        return null
      })
    ),
    Effect.catchTag("ParseError", (err) =>
      Effect.gen(function*() {
        yield* Console.log(`Parse error: ${err.message}`)
        return null
      })
    )
  )

  yield* Console.log(`Result: ${JSON.stringify(user)}`)
  return user
})

Effect.runPromise(program)

Next Steps