Guideline
To start an Effect in the background without blocking the current execution flow, use Effect.fork. This immediately returns a Fiber, which is a handle to the running computation that you can use to manage its lifecycle (e.g., interrupt it or wait for its result).
Rationale
Unlike Effect.all or a direct yield*, which wait for the computation to complete, Effect.fork is a "fire and forget" operation. It starts the effect on a new, concurrent fiber and immediately returns control to the parent fiber.
This is essential for managing long-running background tasks like:
- A web server listener.
- A message queue consumer.
- A periodic cache cleanup job.
The returned Fiber object is your remote control for the background task. You can use Fiber.interrupt to safely stop it (ensuring all its finalizers are run) or Fiber.join to wait for it to complete at some later point.
Good Example
This program forks a background process that logs a "tick" every second. The main process does its own work for 5 seconds and then explicitly interrupts the background logger before exiting.
import { Effect, Fiber } from "effect";
// A long-running effect that logs a message every second, forever
// Effect.forever creates an infinite loop that repeats the effect
// This simulates a background service like a health check or monitoring task
const tickingClock = Effect.log("tick").pipe(
Effect.delay("1 second"), // Wait 1 second between ticks
Effect.forever // Repeat indefinitely - this creates an infinite effect
);
const program = Effect.gen(function* () {
yield* Effect.log("Forking the ticking clock into the background.");
// Start the clock, but don't wait for it.
// Effect.fork creates a new fiber that runs concurrently with the main program
// The main fiber continues immediately without waiting for the background task
// This is essential for non-blocking background operations
const clockFiber = yield* Effect.fork(tickingClock);
// At this point, we have two fibers running:
// 1. The main fiber (this program)
// 2. The background clock fiber (ticking every second)
yield* Effect.log("Main process is now doing other work for 5 seconds...");
// Simulate the main application doing work
// While this sleep happens, the background clock continues ticking
// This demonstrates true concurrency - both fibers run simultaneously
yield* Effect.sleep("5 seconds");
yield* Effect.log("Main process is done. Interrupting the clock fiber.");
// Stop the background process.
// Fiber.interrupt sends an interruption signal to the fiber
// This allows the fiber to perform cleanup operations before terminating
// Without this, the background task would continue running indefinitely
yield* Fiber.interrupt(clockFiber);
// Important: Always clean up background fibers to prevent resource leaks
// In a real application, you might want to:
// 1. Use Fiber.join instead of interrupt to wait for graceful completion
// 2. Handle interruption signals within the background task
// 3. Implement proper shutdown procedures
yield* Effect.log("Program finished.");
// Key concepts demonstrated:
// 1. Fork creates concurrent fibers without blocking
// 2. Background tasks run independently of the main program
// 3. Fiber interruption provides controlled shutdown
// 4. Multiple fibers can run simultaneously on the same thread pool
});
// This example shows how to:
// - Run background tasks that don't block the main program
// - Manage fiber lifecycles (create, run, interrupt)
// - Coordinate between multiple concurrent operations
// - Properly clean up resources when shutting down
Effect.runPromise(program);
Anti-Pattern
The anti-pattern is using Effect.fork when you immediately need the result of the computation. This is an overly complicated and less readable way of just running the effect directly.
import { Effect, Fiber } from "effect";
const someEffect = Effect.succeed(42);
// ❌ WRONG: This is unnecessarily complex.
const program = Effect.gen(function* () {
const fiber = yield* Effect.fork(someEffect);
// You immediately wait for the result, defeating the purpose of forking.
const result = yield* Fiber.join(fiber);
return result;
});
// ✅ CORRECT: Just run the effect directly if you need its result right away.
const simplerProgram = Effect.gen(function* () {
const result = yield* someEffect;
return result;
});