Deferred
A Deferred<A, E> is a write-once, promise-like cell. It starts empty, can be
completed exactly once (with a success value, a typed failure, a defect, or
interruption), and any number of fibers can await it. Awaiters suspend without
blocking until a producer completes the cell, and then they all resume with the
same result. Every completion attempt after the first is a no-op that returns
false, so only the fiber that wins the race actually sets the value.
Reach for a Deferred whenever one fiber must hand a single result, failure, or
“go” signal to one or more other fibers: a one-shot handoff, a one-to-many
broadcast, or a readiness signal.
One fiber computes, others await
Section titled “One fiber computes, others await”Here a producer fiber computes a value and completes the Deferred, while
several forked consumers await it. All consumers suspend until the single
succeed lands, then resume with the same value.
import { Deferred, Effect, Fiber } from "effect"
const program = Effect.gen(function*() { const result = yield* Deferred.make<number>()
// Three consumers, each forked, all blocked on the same cell const consumers = yield* Effect.forEach( [1, 2, 3], (id) => Effect.forkChild( Effect.gen(function*() { const value = yield* Deferred.await(result) yield* Effect.log(`consumer ${id} got ${value}`) return value }) ), { concurrency: "unbounded" } )
// The producer computes once, then broadcasts to every awaiter yield* Effect.sleep("50 millis") yield* Deferred.succeed(result, 42)
return yield* Fiber.joinAll(consumers)})
// => [42, 42, 42], with all three consumers logging "got 42"The completion is stable: a consumer that awaits after the cell is already completed resumes immediately with the stored result rather than blocking.
Readiness signal
Section titled “Readiness signal”A Deferred<void> carrying no meaningful value is a clean way to say “the
system is ready, you may proceed.” A background fiber opens the gate, and other
fibers wait on it before continuing. This is the pattern used to make a
SubscriptionRef stream start before a writer publishes — see
SubscriptionRef.
import { Deferred, Effect, Fiber } from "effect"
const program = Effect.gen(function*() { const ready = yield* Deferred.make<void>()
const worker = yield* Effect.forkChild( Effect.gen(function*() { yield* Deferred.await(ready) // suspends until the gate opens return "started after ready" }) )
// Do setup work, then open the gate exactly once yield* Effect.sleep("20 millis") yield* Deferred.succeed(ready, undefined)
return yield* Fiber.join(worker)})
// => "started after ready"Producer / consumer handoff
Section titled “Producer / consumer handoff”A Deferred<A, E> can carry a failure as well as a value. The consumer’s
await re-raises whatever the producer used to complete the cell, so a failed
handoff fails the awaiting fiber.
import { Deferred, Effect } from "effect"
const program = Effect.gen(function*() { const handoff = yield* Deferred.make<string, Error>()
yield* Effect.forkChild( Effect.gen(function*() { yield* Effect.sleep("10 millis") yield* Deferred.fail(handoff, new Error("upstream unavailable")) }) )
// The error propagates here when we await return yield* Deferred.await(handoff).pipe( Effect.catch((error) => Effect.succeed(`recovered: ${error.message}`)) )})
// => "recovered: upstream unavailable"Deferred vs Latch
Section titled “Deferred vs Latch”A Latch and a Deferred are both
fiber-safe coordination primitives, but they solve different problems:
- A
Latchis a reusable open/close gate that carries no value. You cancloseit again after opening, making it a pause/resume switch for a stream or a worker pool. - A
Deferredis one-shot and carries the value or failure. It can be completed exactly once and never reset; later completion attempts returnfalse.
Use a Latch to gate execution repeatedly; use a Deferred for a single handoff
of a result. For many values over time, use a
Queue or PubSub instead.
Reference
Section titled “Reference”Allocates a new empty Deferred inside an Effect workflow.
import { Deferred, Effect } from "effect"
const program = Effect.gen(function*() { const deferred = yield* Deferred.make<number, string>() yield* Deferred.succeed(deferred, 42) return yield* Deferred.await(deferred)})// => 42makeUnsafe
Section titled “makeUnsafe”Allocates an empty Deferred synchronously, outside the Effect runtime. Use
only in low-level integration code.
import { Deferred } from "effect"
const deferred = Deferred.makeUnsafe<number>()// => an empty Deferred<number>Suspends the current fiber until the Deferred is completed, then resumes with
its stored success, failure, defect, or interruption. Returns immediately if the
cell is already completed.
import { Deferred, Effect } from "effect"
const program = Effect.gen(function*() { const deferred = yield* Deferred.make<number>() yield* Deferred.succeed(deferred, 42) return yield* Deferred.await(deferred)})// => 42Inspects completion without suspending. Returns Option.none() while pending and
Option.some(effect) once completed, where effect is the stored completion
effect you can run to observe the result.
import { Deferred, Effect, Option } from "effect"
const program = Effect.gen(function*() { const deferred = yield* Deferred.make<number>()
const before = yield* Deferred.poll(deferred) console.log(Option.isNone(before)) // => true
yield* Deferred.succeed(deferred, 42)
const after = yield* Deferred.poll(deferred) // after is Option.some(Effect.succeed(42)) return yield* Option.getOrThrow(after) // run the stored effect})// => 42isDone
Section titled “isDone”Returns true if the Deferred has already been completed (with a value, error,
defect, or interruption), false otherwise.
import { Deferred, Effect } from "effect"
const program = Effect.gen(function*() { const deferred = yield* Deferred.make<number>() const before = yield* Deferred.isDone(deferred) // => false yield* Deferred.succeed(deferred, 42) const after = yield* Deferred.isDone(deferred) // => true return [before, after]})// => [false, true]isDoneUnsafe
Section titled “isDoneUnsafe”Synchronous, non-Effect version of isDone. Returns a plain boolean for
low-level code that cannot return an Effect.
import { Deferred } from "effect"
const deferred = Deferred.makeUnsafe<number>()console.log(Deferred.isDoneUnsafe(deferred)) // => falsesucceed
Section titled “succeed”Attempts to complete the Deferred with a success value. Returns true if this
call won the race and completed it, false if it was already completed.
import { Deferred, Effect } from "effect"
const program = Effect.gen(function*() { const deferred = yield* Deferred.make<number>() const won = yield* Deferred.succeed(deferred, 42) // => true const again = yield* Deferred.succeed(deferred, 99) // => false (no-op) return [won, again, yield* Deferred.await(deferred)]})// => [true, false, 42]Like succeed, but the value is computed lazily when the completion effect
runs. Returns a boolean indicating whether this call completed the cell.
import { Deferred, Effect } from "effect"
const program = Effect.gen(function*() { const deferred = yield* Deferred.make<number>() yield* Deferred.sync(deferred, () => 42) // => true return yield* Deferred.await(deferred)})// => 42Attempts to complete the Deferred with a typed failure. Awaiters fail with that
error. Returns true if this call completed the cell.
import { Deferred, Effect } from "effect"
const program = Effect.gen(function*() { const deferred = yield* Deferred.make<number, string>() const won = yield* Deferred.fail(deferred, "boom") // => true return yield* Deferred.await(deferred).pipe(Effect.flip)})// => "boom"failSync
Section titled “failSync”Like fail, but the error is computed lazily when the completion effect runs.
Returns a boolean indicating whether this call completed the cell.
import { Deferred, Effect } from "effect"
const program = Effect.gen(function*() { const deferred = yield* Deferred.make<number, string>() const won = yield* Deferred.failSync(deferred, () => "lazy boom") // => true return won})// => truefailCause
Section titled “failCause”Attempts to complete the Deferred with a full Cause,
letting you carry composite failures (e.g. interruption plus an error). Returns a
boolean indicating whether this call completed the cell.
import { Cause, Deferred, Effect } from "effect"
const program = Effect.gen(function*() { const deferred = yield* Deferred.make<number, string>() const won = yield* Deferred.failCause(deferred, Cause.fail("boom")) // => true return won})// => truefailCauseSync
Section titled “failCauseSync”Like failCause, but the Cause is computed lazily when the completion effect
runs. Returns a boolean indicating whether this call completed the cell.
import { Cause, Deferred, Effect } from "effect"
const program = Effect.gen(function*() { const deferred = yield* Deferred.make<number, string>() const won = yield* Deferred.failCauseSync(deferred, () => Cause.fail("lazy")) // => true return won})// => trueAttempts to complete the Deferred with an unexpected defect. Awaiters die with
that defect. Returns a boolean indicating whether this call completed the cell.
import { Deferred, Effect } from "effect"
const program = Effect.gen(function*() { const deferred = yield* Deferred.make<number>() const won = yield* Deferred.die(deferred, new Error("bug")) // => true return won})// => truedieSync
Section titled “dieSync”Like die, but the defect is computed lazily when the completion effect runs.
Returns a boolean indicating whether this call completed the cell.
import { Deferred, Effect } from "effect"
const program = Effect.gen(function*() { const deferred = yield* Deferred.make<number>() const won = yield* Deferred.dieSync(deferred, () => new Error("lazy bug")) // => true return won})// => trueCompletes the Deferred with an already computed Exit,
covering both the success and failure case in one call. Returns a boolean
indicating whether this call completed the cell.
import { Deferred, Effect, Exit } from "effect"
const program = Effect.gen(function*() { const deferred = yield* Deferred.make<number>() const won = yield* Deferred.done(deferred, Exit.succeed(42)) // => true return yield* Deferred.await(deferred)})// => 42doneUnsafe
Section titled “doneUnsafe”Synchronous, non-Effect version of done that completes the Deferred with a
completion Effect directly. Reserved for low-level code. Returns a plain
boolean.
import { Deferred, Effect } from "effect"
const deferred = Deferred.makeUnsafe<number>()const won = Deferred.doneUnsafe(deferred, Effect.succeed(42)) // => truecomplete
Section titled “complete”Runs the supplied effect once, memoizes its result as an Exit, and completes
the Deferred with that result. Every awaiter observes the same memoized
outcome. Returns a boolean indicating whether this call completed the cell.
import { Deferred, Effect } from "effect"
const program = Effect.gen(function*() { const deferred = yield* Deferred.make<number>() const won = yield* Deferred.complete(deferred, Effect.succeed(42)) // => true return yield* Deferred.await(deferred)})// => 42completeWith
Section titled “completeWith”Stores the supplied effect directly as the completion, without running or memoizing it. Each awaiter runs the stored effect independently. Returns a boolean indicating whether this call completed the cell.
import { Deferred, Effect, Ref } from "effect"
const program = Effect.gen(function*() { const counter = yield* Ref.make(0) const deferred = yield* Deferred.make<number>()
// The increment effect is stored, not run yet yield* Deferred.completeWith( deferred, Ref.updateAndGet(counter, (n) => n + 1) )
const a = yield* Deferred.await(deferred) // runs the effect => 1 const b = yield* Deferred.await(deferred) // runs it AGAIN => 2 return [a, b]})// => [1, 2] (each await re-ran the stored effect)Data-last combinator over an effect: runs the effect and uses its Exit
(success, failure, defect, or interruption) to attempt completion of the given
Deferred. The returned effect cannot fail and succeeds with a boolean.
import { Deferred, Effect } from "effect"
const program = Effect.gen(function*() { const deferred = yield* Deferred.make<number, string>()
// Pipe an effect's result into the Deferred const won = yield* Effect.succeed(42).pipe(Deferred.into(deferred)) // => true
return yield* Deferred.await(deferred)})// => 42interrupt
Section titled “interrupt”Attempts to complete the Deferred as interrupted by the current fiber.
Awaiters are interrupted. Returns a boolean indicating whether this call
completed the cell.
import { Deferred, Effect } from "effect"
const program = Effect.gen(function*() { const deferred = yield* Deferred.make<number>() const won = yield* Deferred.interrupt(deferred) // => true return won})// => trueinterruptWith
Section titled “interruptWith”Like interrupt, but completes as interrupted by a specific fiber id.
Returns a boolean indicating whether this call completed the cell.
import { Deferred, Effect } from "effect"
const program = Effect.gen(function*() { const deferred = yield* Deferred.make<number>() const won = yield* Deferred.interruptWith(deferred, 42) // => true return won})// => trueisDeferred
Section titled “isDeferred”Type guard that checks whether an unknown value is a Deferred. Useful at
runtime boundaries.
import { Deferred } from "effect"
const deferred = Deferred.makeUnsafe<number>()console.log(Deferred.isDeferred(deferred)) // => trueconsole.log(Deferred.isDeferred({})) // => false