Skip to main content
Effect provides a more powerful and type-safe alternative to Promises, with built-in error handling, dependency injection, and resource management.

Why Migrate to Effect?

  • Type-safe errors: Errors are tracked in the type system
  • Dependency management: Built-in context for dependency injection
  • Resource safety: Automatic cleanup with scoped resources
  • Composability: Rich combinators for complex workflows
  • Interruption: First-class support for cancellation

Basic Conversions

Creating Effects from Promises

const fetchUser = async (id: string): Promise<User> => {
  const response = await fetch(`/api/users/${id}`)
  return response.json()
}
Use Effect.promise for promises that never reject, or Effect.tryPromise when you need to handle errors.

Error Handling

async function getUserProfile(id: string): Promise<Profile> {
  try {
    const user = await fetchUser(id)
    const profile = await fetchProfile(user.profileId)
    return profile
  } catch (error) {
    console.error("Failed to fetch profile:", error)
    throw new Error("Profile not found")
  }
}
Effect errors are typed and tracked. Don’t use console.error - handle errors explicitly using Effect.catchAll, Effect.catchTag, or other error combinators.

Sequential Operations

async function processOrder(orderId: string) {
  const order = await fetchOrder(orderId)
  const payment = await processPayment(order)
  const shipment = await createShipment(order, payment)
  return shipment
}
Use Effect.gen for sequential operations - it’s similar to async/await but with better type safety and composability.

Parallel Operations

async function getDashboardData(userId: string) {
  const [user, orders, notifications] = await Promise.all([
    fetchUser(userId),
    fetchOrders(userId),
    fetchNotifications(userId)
  ])
  return { user, orders, notifications }
}

Timeouts

const withTimeout = <T>(promise: Promise<T>, ms: number): Promise<T> => {
  return Promise.race([
    promise,
    new Promise<never>((_, reject) =>
      setTimeout(() => reject(new Error("Timeout")), ms)
    )
  ])
}

const result = await withTimeout(fetchUser("123"), 5000)

Retries

async function fetchWithRetry<T>(
  fn: () => Promise<T>,
  maxRetries: number
): Promise<T> {
  let lastError: unknown
  for (let i = 0; i < maxRetries; i++) {
    try {
      return await fn()
    } catch (error) {
      lastError = error
      await new Promise(resolve => setTimeout(resolve, 1000 * (i + 1)))
    }
  }
  throw lastError
}

Resource Management

class Database {
  private connection: Connection | null = null

  async connect() {
    this.connection = await createConnection()
  }

  async query(sql: string) {
    if (!this.connection) throw new Error("Not connected")
    return this.connection.query(sql)
  }

  async disconnect() {
    await this.connection?.close()
    this.connection = null
  }
}

async function runQuery() {
  const db = new Database()
  try {
    await db.connect()
    return await db.query("SELECT * FROM users")
  } finally {
    await db.disconnect()
  }
}
Always use Effect.acquireRelease or Effect.scoped for resource management. This ensures cleanup happens even on errors or interruptions.

Running Effects

fetchUser("123")
  .then(user => console.log(user))
  .catch(error => console.error(error))

// Or with async/await
try {
  const user = await fetchUser("123")
  console.log(user)
} catch (error) {
  console.error(error)
}

Migration Strategy

  1. Start at the boundaries: Wrap external APIs with Effect first
  2. Convert incrementally: Use Effect.promise to call existing async code
  3. Type your errors: Define error types for better type safety
  4. Use services: Leverage dependency injection for testability
  5. Test thoroughly: Effect code is easier to test with mocked dependencies
You can mix Effect and Promise code during migration. Use Effect.runPromise to convert Effect to Promise when needed.

Common Patterns

Promise.allSettled

const results = await Promise.allSettled([
  fetchUser("1"),
  fetchUser("2"),
  fetchUser("3")
])

Conditional Execution

const user = await fetchUser(id)
if (user.isPremium) {
  return await fetchPremiumContent()
}
return await fetchBasicContent()

Next Steps

Build docs developers (and LLMs) love