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:
- Redundant requests - Race multiple servers, use fastest response
- Timeouts - Fail fast if operation takes too long
- 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"),
])