Guideline
Fibers are Effect's lightweight threads. They're cheap to create (thousands are fine), automatically managed, and can be interrupted cleanly.
Rationale
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
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)
})
Fiber vs Thread
| Aspect | OS Thread | Effect Fiber |
|---|---|---|
| Memory | ~1MB stack | ~few KB |
| Creation | Expensive | Cheap |
| Scheduling | OS kernel | Effect runtime |
| Interruption | Messy | Clean |
| Count | Hundreds | Thousands |
Key Operations
| Operation | What it does |
|---|---|
Effect.fork(effect) |
Start effect on new fiber |
Fiber.join(fiber) |
Wait for fiber to complete |
Fiber.interrupt(fiber) |
Cancel the fiber |
Fiber.poll(fiber) |
Check status without waiting |
Fiber.await(fiber) |
Get Exit (success or failure) |
Mental Model
Think of fibers as "lightweight async tasks":
Main Fiber
│
├── fork ──► Child Fiber 1 ──► runs independently
│
├── fork ──► Child Fiber 2 ──► runs independently
│
└── join ◄── waits for children