Deferred
On this page
A Deferred<A, E>
is a special subtype of Effect
that acts as a variable, but with some unique characteristics. It can only be set once, making it a powerful synchronization tool for managing asynchronous operations.
A Deferred
is essentially a synchronization primitive that represents a value that may not be available immediately. When you create a Deferred
, it starts with an empty value. Later on, you can complete it exactly once with either a success value (A
) or a failure value (E
). Once completed, a Deferred
can never be modified or emptied again.
Common Use Cases
Deferred
becomes incredibly useful when you need to wait for something specific to happen in your program. It's ideal for scenarios where you want one part of your code to signal another part when it's ready. Here are a few common use cases:
-
Coordinating Fibers: When you have multiple concurrent tasks (fibers) and need to coordinate their actions,
Deferred
can help one fiber signal to another when it has completed its task. -
Synchronization: Anytime you want to ensure that one piece of code doesn't proceed until another piece of code has finished its work,
Deferred
can provide the synchronization you need. -
Handing Over Work: You can use
Deferred
to hand over work from one fiber to another. For example, one fiber can prepare some data, and then a second fiber can continue processing it. -
Suspending Execution: When you want a fiber to pause its execution until some condition is met, a
Deferred
can be used to block it until the condition is satisfied.
When a fiber calls await
on a Deferred
, it essentially blocks until that Deferred
is completed with either a value or an error. Importantly, in Effect, blocking fibers don't actually block the main thread; they block only semantically. While one fiber is blocked, the underlying thread can execute other fibers, ensuring efficient concurrency.
A Deferred
in Effect is conceptually similar to JavaScript's Promise
. The key difference is that Deferred
has two type parameters (E
and A
) instead of just one. This allows Deferred
to represent both successful results (A
) and errors (E
).
Operations
Creating
You can create a Deferred
using Deferred.make<A, E>()
. This returns an Effect<Deferred<A, E>>
, which describes the creation of a Deferred
. Note that Deferred
s can only be created within an Effect
because creating them involves effectful memory allocation, which must be managed safely within an Effect
.
Awaiting
To retrieve a value from a Deferred
, you can use Deferred.await
. This operation suspends the calling fiber until the Deferred
is completed with a value or an error.
ts
import {Effect ,Deferred } from "effect"consteffectDeferred =Deferred .make <string,Error >()consteffectGet =effectDeferred .pipe (Effect .andThen ((deferred ) =>Deferred .await (deferred )))
ts
import {Effect ,Deferred } from "effect"consteffectDeferred =Deferred .make <string,Error >()consteffectGet =effectDeferred .pipe (Effect .andThen ((deferred ) =>Deferred .await (deferred )))
Completing
You can complete a Deferred<A, E>
in different ways:
There are several ways to complete a Deferred
:
Deferred.succeed
: Completes theDeferred
successfully with a value of typeA
.Deferred.done
: Completes theDeferred
with anExit<A, E>
type.Deferred.complete
: Completes theDeferred
with the result of an effectEffect<A, E>
.Deferred.completeWith
: Completes theDeferred
with an effectEffect<A, E>
. This effect will be executed by each waiting fiber, so use it carefully.Deferred.fail
: Fails theDeferred
with an error of typeE
.Deferred.die
: Defects theDeferred
with a user-defined error.Deferred.failCause
: Fails or defects theDeferred
with aCause<E>
.Deferred.interrupt
: Interrupts theDeferred
. This can be used to forcefully stop or interrupt the waiting fibers.
Here's an example that demonstrates the usage of these completion methods:
ts
import {Effect ,Deferred ,Exit ,Cause } from "effect"constprogram =Effect .gen (function* () {constdeferred = yield*Deferred .make <number, string>()// Completing the Deferred in various waysyield*Deferred .succeed (deferred , 1).pipe (Effect .fork )yield*Deferred .complete (deferred ,Effect .succeed (2)).pipe (Effect .fork )yield*Deferred .completeWith (deferred ,Effect .succeed (3)).pipe (Effect .fork )yield*Deferred .done (deferred ,Exit .succeed (4)).pipe (Effect .fork )yield*Deferred .fail (deferred , "5").pipe (Effect .fork )yield*Deferred .failCause (deferred ,Cause .die (newError ("6"))).pipe (Effect .fork )yield*Deferred .die (deferred , newError ("7")).pipe (Effect .fork )yield*Deferred .interrupt (deferred ).pipe (Effect .fork )// Awaiting the Deferred to get its valueconstvalue = yield*Deferred .await (deferred )returnvalue })Effect .runPromise (program ).then (console .log ,console .error ) // Output: 1
ts
import {Effect ,Deferred ,Exit ,Cause } from "effect"constprogram =Effect .gen (function* () {constdeferred = yield*Deferred .make <number, string>()// Completing the Deferred in various waysyield*Deferred .succeed (deferred , 1).pipe (Effect .fork )yield*Deferred .complete (deferred ,Effect .succeed (2)).pipe (Effect .fork )yield*Deferred .completeWith (deferred ,Effect .succeed (3)).pipe (Effect .fork )yield*Deferred .done (deferred ,Exit .succeed (4)).pipe (Effect .fork )yield*Deferred .fail (deferred , "5").pipe (Effect .fork )yield*Deferred .failCause (deferred ,Cause .die (newError ("6"))).pipe (Effect .fork )yield*Deferred .die (deferred , newError ("7")).pipe (Effect .fork )yield*Deferred .interrupt (deferred ).pipe (Effect .fork )// Awaiting the Deferred to get its valueconstvalue = yield*Deferred .await (deferred )returnvalue })Effect .runPromise (program ).then (console .log ,console .error ) // Output: 1
When you complete a Deferred
, it results in an Effect<boolean>
. This effect returns true
if the Deferred
value has been set and false
if it was already set before completion. This can be useful for checking the state of the Deferred
.
Here's an example demonstrating the state change of a Deferred
:
ts
import {Effect ,Deferred } from "effect"constprogram =Effect .gen (function* () {constdeferred = yield*Deferred .make <number, string>()constb1 = yield*Deferred .fail (deferred , "oh no!")constb2 = yield*Deferred .succeed (deferred , 1)return [b1 ,b2 ]})Effect .runPromise (program ).then (console .log ) // Output: [ true, false ]
ts
import {Effect ,Deferred } from "effect"constprogram =Effect .gen (function* () {constdeferred = yield*Deferred .make <number, string>()constb1 = yield*Deferred .fail (deferred , "oh no!")constb2 = yield*Deferred .succeed (deferred , 1)return [b1 ,b2 ]})Effect .runPromise (program ).then (console .log ) // Output: [ true, false ]
Polling
Sometimes, you may want to check whether a Deferred
has been completed without causing the fiber to suspend. To achieve this, you can use the Deferred.poll
method. Here's how it works:
Deferred.poll
returns anOption<Effect<A, E>>
.- If the
Deferred
is not yet completed, it returnsNone
. - If the
Deferred
is completed, it returnsSome
, which contains the result or error.
- If the
Additionally, you can use the Deferred.isDone
method, which returns an Effect<boolean>
. This effect evaluates to true
if the Deferred
is already completed, allowing you to quickly check the completion status.
Here's a practical example:
ts
import {Effect ,Deferred } from "effect"constprogram =Effect .gen (function* () {constdeferred = yield*Deferred .make <number, string>()// Polling the Deferredconstdone1 = yield*Deferred .poll (deferred )// Checking if the Deferred is already completedconstdone2 = yield*Deferred .isDone (deferred )return [done1 ,done2 ]})Effect .runPromise (program ).then (console .log ) // Output: [ none(), false ]
ts
import {Effect ,Deferred } from "effect"constprogram =Effect .gen (function* () {constdeferred = yield*Deferred .make <number, string>()// Polling the Deferredconstdone1 = yield*Deferred .poll (deferred )// Checking if the Deferred is already completedconstdone2 = yield*Deferred .isDone (deferred )return [done1 ,done2 ]})Effect .runPromise (program ).then (console .log ) // Output: [ none(), false ]
In this example, we first create a Deferred
and then use Deferred.poll
to check its completion status. Since it's not completed yet, done1
is none()
. We also use Deferred.isDone
to confirm that the Deferred
is indeed not completed, so done2
is false
.
Example: Using Deferred to Coordinate Two Fibers
Here's a scenario where we use a Deferred
to hand over a value between two fibers:
ts
import {Effect ,Deferred ,Fiber } from "effect"constprogram =Effect .gen (function* () {constdeferred = yield*Deferred .make <string, string>()// Fiber A: Set the Deferred value after waiting for 1 secondconstsendHelloWorld =Effect .gen (function* () {yield*Effect .sleep ("1 second")return yield*Deferred .succeed (deferred , "hello world")})// Fiber B: Wait for the Deferred and print the valueconstgetAndPrint =Effect .gen (function* () {consts = yield*Deferred .await (deferred )console .log (s )returns })// Run both fibers concurrentlyconstfiberA = yield*Effect .fork (sendHelloWorld )constfiberB = yield*Effect .fork (getAndPrint )// Wait for both fibers to completereturn yield*Fiber .join (Fiber .zip (fiberA ,fiberB ))})Effect .runPromise (program ).then (console .log ,console .error )/*Output:hello world[ true, "hello world" ]*/
ts
import {Effect ,Deferred ,Fiber } from "effect"constprogram =Effect .gen (function* () {constdeferred = yield*Deferred .make <string, string>()// Fiber A: Set the Deferred value after waiting for 1 secondconstsendHelloWorld =Effect .gen (function* () {yield*Effect .sleep ("1 second")return yield*Deferred .succeed (deferred , "hello world")})// Fiber B: Wait for the Deferred and print the valueconstgetAndPrint =Effect .gen (function* () {consts = yield*Deferred .await (deferred )console .log (s )returns })// Run both fibers concurrentlyconstfiberA = yield*Effect .fork (sendHelloWorld )constfiberB = yield*Effect .fork (getAndPrint )// Wait for both fibers to completereturn yield*Fiber .join (Fiber .zip (fiberA ,fiberB ))})Effect .runPromise (program ).then (console .log ,console .error )/*Output:hello world[ true, "hello world" ]*/
In this example, we have two fibers, fiberA
and fiberB
, that communicate using a Deferred
:
fiberA
sets theDeferred
value to "hello world" after waiting for 1 second.fiberB
waits for theDeferred
to be completed and then prints the received value to the console.
By running both fibers concurrently and using the Deferred
as a synchronization point, we can ensure that fiberB
only proceeds after fiberA
has completed its task. This coordination mechanism allows you to hand over values or coordinate work between different parts of your program effectively.