| name | effect-patterns-concurrency-getting-started |
| description | Effect-TS patterns for Concurrency Getting Started. Use when working with concurrency getting started in Effect-TS applications. |
Effect-TS Patterns: Concurrency Getting Started
This skill provides 3 curated Effect-TS patterns for concurrency getting started.
Use this skill when working on tasks related to:
- concurrency getting started
- Best practices in Effect-TS applications
- Real-world patterns and solutions
🟢 Beginner Patterns
Race Effects and Handle Timeouts
Rule: Use Effect.race for fastest-wins, Effect.timeout for time limits.
Good Example:
import { Effect, Option } from "effect"
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)
})
const slowOperation = Effect.gen(function* () {
yield* Effect.sleep("5 seconds")
return "Finally done"
})
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}`)
}
})
const withFallback = slowOperation.pipe(
Effect.timeoutTo({
duration: "1 second",
onTimeout: () => Effect.succeed("Using cached value"),
})
)
Effect.runPromise(withFallback).then((result) => {
console.log(result)
})
class TimeoutError {
readonly _tag = "TimeoutError"
}
const failOnTimeout = slowOperation.pipe(
Effect.timeoutFail({
duration: "1 second",
onTimeout: () => new TimeoutError(),
})
)
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}`)
})
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
})
Rationale:
Use Effect.race when you want the first result from competing effects. Use Effect.timeout to limit how long an effect can run.
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
Understanding Fibers
Rule: Fibers are lightweight threads managed by Effect, enabling efficient concurrency without OS thread overhead.
Good Example:
import { Effect, Fiber } from "effect"
const myEffect = Effect.gen(function* () {
yield* Effect.log("Hello from a fiber!")
yield* Effect.sleep("100 millis")
return 42
})
Effect.runPromise(myEffect)
const withFork = Effect.gen(function* () {
yield* Effect.log("Main fiber starting")
const fiber = yield* Effect.fork(
Effect.gen(function* () {
yield* .()
* .()
* .()
})
)
* .()
* .()
* .()
result = * .(fiber)
* .()
})
.(withFork)
fiberOps = .(* () {
fiber = * .(
.(* () {
* .()
})
)
poll = * .(fiber)
* .()
result = * .(fiber)
* .()
})
Rationale:
Fibers are Effect's lightweight threads. They're cheap to create (thousands are fine), automatically managed, and can be interrupted cleanly.
Unlike OS threads:
- Lightweight - Create thousands without performance issues
- Cooperative - Yield control at effect boundaries
- Interruptible - Can be cancelled cleanly
- Structured - Parent fibers manage children
Your First Parallel Operation
Rule: Use Effect.all with concurrency option to run independent effects in parallel.
Good Example:
import { Effect } from "effect"
const fetchUser = Effect.gen(function* () {
yield* Effect.sleep("100 millis")
return { id: 1, name: "Alice" }
})
const fetchProducts = Effect.gen(function* () {
yield* Effect.sleep("150 millis")
return [{ id: 1, name: "Widget" }, { id: 2, name: "Gadget" }]
})
const fetchCart = Effect.gen(function* () {
yield* Effect.sleep("80 millis")
return { items: 3, total: 99.99 }
})
const sequential = .([fetchUser, fetchProducts, fetchCart])
parallel = .(
[fetchUser, fetchProducts, fetchCart],
{ : }
)
limited = .(
[fetchUser, fetchProducts, fetchCart],
{ : }
)
demo = .(* () {
start = .()
[user, products, cart] = * parallel
elapsed = .() - start
* .()
* .()
* .()
* .()
})
.(demo)
Rationale:
Use Effect.all with { concurrency: "unbounded" } to run independent effects in parallel. Without the option, effects run sequentially.
Parallel execution speeds up independent operations:
- Fetch multiple APIs - Get user, products, cart simultaneously
- Process files - Read multiple files at once
- Database queries - Run independent queries in parallel