Skip to content

Latest commit

 

History

History
202 lines (160 loc) · 4.88 KB

File metadata and controls

202 lines (160 loc) · 4.88 KB
title Race Effects and Handle Timeouts
id concurrency-race-timeout
skillLevel beginner
applicationPatternId concurrency-getting-started
summary Race multiple effects to get the fastest result, or add timeouts to prevent hanging operations.
tags
concurrency
race
timeout
getting-started
rule
description
Use Effect.race for fastest-wins, Effect.timeout for time limits.
author PaulJPhilp
related
concurrency-hello-world
concurrency-fork-basics
lessonOrder 1

Guideline

Use Effect.race when you want the first result from competing effects. Use Effect.timeout to limit how long an effect can run.


Rationale

Racing and timeouts prevent your app from hanging:

  1. Redundant requests - Race multiple servers, use fastest response
  2. Timeouts - Fail fast if operation takes too long
  3. Fallbacks - Try fast path, fall back to slow path

Good Example

import { Effect, Option } from "effect"

// ============================================
// BASIC RACE: First one wins
// ============================================

const server1 = Effect.gen(function* () {
  yield* Effect.sleep("100 millis")
  return "Response from server 1"
})

const server2 = Effect.gen(function* () {
  yield* Effect.sleep("50 millis")
  return "Response from server 2"
})

const raceServers = Effect.race(server1, server2)

Effect.runPromise(raceServers).then((result) => {
  console.log(result) // "Response from server 2" (faster)
})

// ============================================
// BASIC TIMEOUT: Limit execution time
// ============================================

const slowOperation = Effect.gen(function* () {
  yield* Effect.sleep("5 seconds")
  return "Finally done"
})

// Returns Option.none if timeout
const withTimeout = slowOperation.pipe(
  Effect.timeout("1 second")
)

Effect.runPromise(withTimeout).then((result) => {
  if (Option.isNone(result)) {
    console.log("Operation timed out")
  } else {
    console.log(`Got: ${result.value}`)
  }
})

// ============================================
// TIMEOUT WITH FALLBACK
// ============================================

const withFallback = slowOperation.pipe(
  Effect.timeoutTo({
    duration: "1 second",
    onTimeout: () => Effect.succeed("Using cached value"),
  })
)

Effect.runPromise(withFallback).then((result) => {
  console.log(result) // "Using cached value"
})

// ============================================
// TIMEOUT FAIL: Throw error on timeout
// ============================================

class TimeoutError {
  readonly _tag = "TimeoutError"
}

const failOnTimeout = slowOperation.pipe(
  Effect.timeoutFail({
    duration: "1 second",
    onTimeout: () => new TimeoutError(),
  })
)

// ============================================
// RACE ALL: Multiple competing effects
// ============================================

const fetchFromCache = Effect.gen(function* () {
  yield* Effect.sleep("10 millis")
  return { source: "cache", data: "cached data" }
})

const fetchFromDB = Effect.gen(function* () {
  yield* Effect.sleep("100 millis")
  return { source: "db", data: "fresh data" }
})

const fetchFromAPI = Effect.gen(function* () {
  yield* Effect.sleep("200 millis")
  return { source: "api", data: "api data" }
})

const raceAll = Effect.raceAll([fetchFromCache, fetchFromDB, fetchFromAPI])

Effect.runPromise(raceAll).then((result) => {
  console.log(`Winner: ${result.source}`) // "cache"
})

// ============================================
// PRACTICAL: API with timeout and fallback
// ============================================

const fetchWithResilience = (url: string) =>
  Effect.gen(function* () {
    const response = yield* Effect.tryPromise(() =>
      fetch(url).then((r) => r.json())
    ).pipe(
      Effect.timeout("3 seconds"),
      Effect.flatMap((opt) =>
        Option.isSome(opt)
          ? Effect.succeed(opt.value)
          : Effect.succeed({ error: "timeout", cached: true })
      )
    )
    
    return response
  })

Timeout Methods

Method Behavior
Effect.timeout(duration) Returns Option<A>, None on timeout
Effect.timeoutTo({duration, onTimeout}) Custom fallback effect
Effect.timeoutFail({duration, onTimeout}) Fail with error on timeout

Race Methods

Method Behavior
Effect.race(a, b) First of two effects
Effect.raceAll([a, b, c]) First of many effects
Effect.raceFirst(a, b) First to complete (even if error)

Common Patterns

// Try fast, fall back to slow
Effect.race(
  fastPath.pipe(Effect.timeout("100 millis")),
  slowPath
)

// Request with hard timeout
fetch(url).pipe(
  Effect.timeoutFail({
    duration: "5 seconds",
    onTimeout: () => new RequestTimeoutError()
  })
)

// Redundant requests for reliability
Effect.raceAll([
  fetchFromServer("us-east"),
  fetchFromServer("us-west"),
  fetchFromServer("eu-west"),
])