
effect-patterns-concurrency-getting-started
by PaulJPhilp
A community-driven knowledge base of practical patterns for Effect-TS.
SKILL.md
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"
// ============================================
// 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
})
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"
// ============================================
// WHAT IS A FIBER?
// ============================================
// A fiber is a running effect. When you run an effect,
// it executes on a fiber.
const myEffect = Effect.gen(function* () {
yield* Effect.log("Hello from a fiber!")
yield* Effect.sleep("100 millis")
return 42
})
// This runs myEffect on the "main" fiber
Effect.runPromise(myEffect)
// ============================================
// FORKING: Create a new fiber
// ============================================
const withFork = Effect.gen(function* () {
yield* Effect.log("Main fiber starting")
// Fork creates a new fiber that runs independently
const fiber = yield* Effect.fork(
Effect.gen(function* () {
yield* Effect.log("Child fiber running")
yield* Effect.sleep("200 millis")
yield* Effect.log("Child fiber done")
return "child result"
})
)
yield* Effect.log("Main fiber continues immediately")
yield* Effect.sleep("100 millis")
yield* Effect.log("Main fiber waiting for child...")
// Wait for the forked fiber to complete
const result = yield* Fiber.join(fiber)
yield* Effect.log(`Got result: ${result}`)
})
Effect.runPromise(withFork)
/*
Output:
Main fiber starting
Child fiber running
Main fiber continues immediately
Main fiber waiting for child...
Child fiber done
Got result: child result
*/
// ============================================
// FIBER OPERATIONS
// ============================================
const fiberOps = Effect.gen(function* () {
const fiber = yield* Effect.fork(
Effect.gen(function* () {
yield* Effect.sleep("1 second")
return "done"
})
)
// Check if fiber is done (non-blocking)
const poll = yield* Fiber.poll(fiber)
yield* Effect.log(`Poll result: ${poll}`) // None (still running)
// Wait for completion
const result = yield* Fiber.join(fiber)
yield* Effect.log(`Join result: ${result}`)
// Or interrupt if taking too long
// yield* Fiber.interrupt(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"
// Simulate async operations
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 }
})
// ============================================
// SEQUENTIAL: One after another (~330ms)
// ============================================
const sequential = Effect.all([fetchUser, fetchProducts, fetchCart])
// ============================================
// PARALLEL: All at once (~150ms)
// ============================================
const parallel = Effect.all(
[fetchUser, fetchProducts, fetchCart],
{ concurrency: "unbounded" }
)
// ============================================
// PARALLEL WITH LIMIT: Max 2 at a time
// ============================================
const limited = Effect.all(
[fetchUser, fetchProducts, fetchCart],
{ concurrency: 2 }
)
// ============================================
// DEMO
// ============================================
const demo = Effect.gen(function* () {
const start = Date.now()
const [user, products, cart] = yield* parallel
const elapsed = Date.now() - start
yield* Effect.log(`Fetched in ${elapsed}ms`)
yield* Effect.log(`User: ${user.name}`)
yield* Effect.log(`Products: ${products.length}`)
yield* Effect.log(`Cart total: $${cart.total}`)
})
Effect.runPromise(demo)
// Output: Fetched in ~150ms (not ~330ms!)
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
Score
Total Score
Based on repository quality metrics
SKILL.mdファイルが含まれている
ライセンスが設定されている
100文字以上の説明がある
GitHub Stars 500以上
1ヶ月以内に更新
10回以上フォークされている
オープンIssueが50未満
プログラミング言語が設定されている
1つ以上のタグが設定されている
Reviews
Reviews coming soon


