A Latch is a synchronization tool that works like a gate, letting fibers wait until the latch is opened before they continue. The latch can be either open or closed:
When closed, fibers that reach the latch wait until it opens.
When open, fibers pass through immediately.
Once opened, a latch typically stays open, although you can close it again if needed
Imagine an application that processes requests only after completing an initial setup (like loading configuration data or establishing a database connection).
You can create a latch in a closed state while the setup is happening.
Any incoming requests, represented as fibers, would wait at the latch until it opens.
Once the setup is finished, you call latch.open so the requests can proceed.
The Latch Interface
A Latch includes several operations that let you control and observe its state:
Operation
Description
whenOpen
Runs a given effect only if the latch is open, otherwise, waits until it opens.
open
Opens the latch so that any waiting fibers can proceed.
close
Closes the latch, causing fibers to wait when they reach this latch in the future.
await
Suspends the current fiber until the latch is opened. If the latch is already open, returns immediately.
release
Allows waiting fibers to continue without permanently opening the latch.
Creating a Latch
Use the Effect.makeLatch function to create a latch in an open or closed state by passing a boolean. The default is false, which means it starts closed.
Example (Creating and Using a Latch)
In this example, the latch starts closed. A fiber logs “open sesame” only when the latch is open. After waiting for one second, the latch is opened, releasing the fiber:
1
import {
import Console
Console,
import Effect
@since ― 2.0.0
@since ― 2.0.0
@since ― 2.0.0
Effect } from"effect"
2
3
// A generator function that demonstrates latch usage
Provides a way to write effectful code using generator functions, simplifying
control flow and error handling.
When to Use
Effect.gen allows you to write code that looks and behaves like synchronous
code, but it can handle asynchronous tasks, errors, and complex control flow
(like loops and conditions). It helps make asynchronous code more readable
and easier to manage.
The generator functions work similarly to async/await but with more
explicit control over the execution of effects. You can yield* values from
effects and return the final result at the end.
Latch.whenOpen: <A, E, R>(self:Effect.Effect<A, E, R>) => Effect.Effect<A, E, R>
Runs the given effect only when the latch is open.
Details
This function ensures that the provided effect executes only if the latch
is open. If the latch is closed, the fiber will wait until it opens.
whenOpen, // Waits for the latch to open
11
import Effect
@since ― 2.0.0
@since ― 2.0.0
@since ― 2.0.0
Effect.
constfork: <A, E, R>(self:Effect.Effect<A, E, R>) =>Effect.Effect<RuntimeFiber<A, E>, never, R>
Creates a new fiber to run an effect concurrently.
Details
This function takes an effect and forks it into a separate fiber, allowing it
to run concurrently without blocking the original effect. The new fiber
starts execution immediately after being created, and the fiber object is
returned immediately without waiting for the effect to begin. This is useful
when you want to run tasks concurrently while continuing other tasks in the
parent fiber.
The forked fiber is attached to the parent fiber's scope. This means that
when the parent fiber terminates, the child fiber will also be terminated
automatically. This feature, known as "auto supervision," ensures that no
fibers are left running unintentionally. If you prefer not to have this auto
supervision behavior, you can use
forkDaemon
or
forkIn
.
When to Use
Use this function when you need to run an effect concurrently without
blocking the current execution flow. For example, you might use it to launch
background tasks or concurrent computations. However, working with fibers can
be complex, so before using this function directly, you might want to explore
higher-level functions like
raceWith
,
zip
, or others that can
manage concurrency for you.
@see ― forkWithErrorHandler for a version that allows you to handle errors.
@example
import { Effect } from"effect"
constfib= (n:number):Effect.Effect<number> =>
n <2
? Effect.succeed(n)
: Effect.zipWith(fib(n -1), fib(n -2), (a, b) => a + b)
Suspends the execution of an effect for a specified Duration.
Details
This function pauses the execution of an effect for a given duration. It is
asynchronous, meaning that it does not block the fiber executing the effect.
Instead, the fiber is suspended during the delay period and can resume once
the specified time has passed.
The duration can be specified using various formats supported by the
Duration module, such as a string ("2 seconds") or numeric value
representing milliseconds.
@example
import { Effect } from"effect"
constprogram= Effect.gen(function*() {
console.log("Starting task...")
yield* Effect.sleep("3 seconds") // Waits for 3 seconds
console.log("Task completed!")
})
// Effect.runFork(program)
// Output:
// Starting task...
// Task completed!
@since ― 2.0.0
sleep("1 second")
16
17
// Open the latch, releasing the fiber
18
yield*
constlatch:Effect.Latch
latch.
Latch.open: Effect.Effect<void, never, never>
Opens the latch, releasing all fibers waiting on it.
Details
Once the latch is opened, it remains open. Any fibers waiting on await
will be released and can continue execution.
Runs an effect in the background, returning a fiber that can be observed or
interrupted.
Unless you specifically need a Promise or synchronous operation, runFork
is a good default choice.
Details
This function is the foundational way to execute an effect in the background.
It creates a "fiber," a lightweight, cooperative thread of execution that can
be observed (to access its result), interrupted, or joined. Fibers are useful
for concurrent programming and allow effects to run independently of the main
program flow.
Once the effect is running in a fiber, you can monitor its progress, cancel
it if necessary, or retrieve its result when it completes. If the effect
fails, the fiber will propagate the failure, which you can observe and
handle.
When to Use
Use this function when you need to run an effect in the background,
especially if the effect is long-running or performs periodic tasks. It's
suitable for tasks that need to run independently but might still need
observation or management, like logging, monitoring, or scheduled tasks.
This function is ideal if you don't need the result immediately or if the
effect is part of a larger concurrent workflow.
A latch is good when you have a one-time event or condition that determines whether fibers can proceed. For example, you might use a latch to block all fibers until a setup step is finished, and then open the latch so everyone can continue.
A semaphore with one lock (often called a binary semaphore or a mutex) is usually for mutual exclusion: it ensures that only one fiber at a time accesses a shared resource or section of code. Once a fiber acquires the lock, no other fiber can enter the protected area until the lock is released.
In short:
Use a latch if you’re gating a set of fibers on a specific event (“Wait here until this becomes true”).
Use a semaphore (with one lock) if you need to ensure only one fiber at a time is in a critical section or using a shared resource.