Skip to content

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.

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.

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"

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"

A Latch and a Deferred are both fiber-safe coordination primitives, but they solve different problems:

  • A Latch is a reusable open/close gate that carries no value. You can close it again after opening, making it a pause/resume switch for a stream or a worker pool.
  • A Deferred is one-shot and carries the value or failure. It can be completed exactly once and never reset; later completion attempts return false.

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.

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)
})
// => 42

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)
})
// => 42

Inspects 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
})
// => 42

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]

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)) // => false

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)
})
// => 42

Attempts 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"

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
})
// => true

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
})
// => true

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
})
// => true

Attempts 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
})
// => true

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
})
// => true

Completes 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)
})
// => 42

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)) // => true

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)
})
// => 42

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)
})
// => 42

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
})
// => true

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
})
// => true

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)) // => true
console.log(Deferred.isDeferred({})) // => false