Deferred — Awaitable Async Values
142 B minified + gzipped
A snapshot exporter asks a primary and a replica source for the same reading. Each source already turns its own network failure into a fallback string, so what remains is a value that arrives later. Passing those promises around leaves the reader guessing which stage can still reject, and a rejection that is never observed surfaces as an unhandled promise warning far from the code that dropped it.
Core Behavior
Section titled “Core Behavior”Deferred<A> is a one-shot awaitable value for asynchronous work that resolves without rejecting.
awaityields the value. The type has no rejection handler, so there is no.catch()to call.map,chain, andtapreshape the settled value and keep it awaitable; they start no work.Deferred.allwaits for every input and preserves input order.Deferred.racesettles with the first input.Deferred.from.Promiseassumes its input resolves, andDeferred.to.Promiseproduces a native promise for an API that requires one.
Use Deferred when the work is already running and only the value is missing. A
Task describes work that has not started and produces a deferred value when it is
run. Reading the Types covers the split between definitions, instances, and
settled values, and the choosing guide lists the shapes that produce one.
Example: Snapshot Status
Section titled “Example: Snapshot Status”A snapshot exporter waits for readings from a primary and a replica source. Each source converts its
own request failure into the string "unavailable" before the exporter sees it, so the exporter
combines values that are already in progress.
Deferred.all resolves only after every input resolves, and it keeps the input order. The diagram
shows the state of the combined value, not the work of the sources:
flowchart TD
Inputs["Readings already running"] --> Pending{"All readings resolved?"}
Pending -->|No| Wait["Combined value stays pending"]
Wait --> Pending
Pending -->|Yes| Ordered["Readings in input order"]
Collecting Status Readings
Section titled “Collecting Status Readings”The .catch runs before the promise is wrapped, which keeps the rejection channel out of the
awaitable type:
The calls to readStatus start the requests. Deferred.all only combines their results; it starts
nothing and cancels nothing.
A pipeline can reshape the combined value without converting it back to a promise. The mapper receives the resolved pair and returns a new value, and the result stays awaitable:
Owning the Refresh
Section titled “Owning the Refresh”A dashboard can refresh the same two readings when the user selects a different snapshot. Op takes
the composed value as one task and decides what a second refresh does to the first:
The manager settles run() to one outcome, so the dashboard awaits the same kind of value it
awaited before. The Op guide covers strategies that replace or queue overlapping
work.
Core Operations
Section titled “Core Operations”Creating and Converting
Section titled “Creating and Converting”| Signature | What it does | Return type |
|---|---|---|
Deferred.from.Promise(promise) |
Wraps a promise or thenable that will not reject. | Deferred<A> |
Deferred.to.Promise(deferred) |
Converts an awaitable value to a native promise. | Promise<A> |
Transforming
Section titled “Transforming”| Signature | What it does | Return type |
|---|---|---|
Deferred.map(transform) |
Transforms the resolved value. | Deferred<B> |
Deferred.chain(transform) |
Runs the next deferred step after this one resolves. | Deferred<B> |
Deferred.tap(sideEffect) |
Observes the resolved value and passes it through. | Deferred<A> |
Combining and Reading
Section titled “Combining and Reading”| Signature | What it does | Return type |
|---|---|---|
Deferred.all(values) |
Waits for every value, preserving input order. | Deferred of the tuple |
Deferred.race(values) |
Settles with the first value to resolve. | Deferred<A> |
await deferred |
Reads the resolved value. | A |
Failure Behavior and Edge Cases
Section titled “Failure Behavior and Edge Cases”Deferred.from.Promise does not catch rejections. If its input rejects, the wrapper never resolves
and the rejection is unhandled. Handle the failure before wrapping, as readStatus does with
.catch, or use Task.Result.tryCatch for work that can fail.
Deferred.race([]) stays pending because no value can resolve it. A race does not cancel its other
inputs after one resolves; cancelling the work is the producer’s job, through the AbortSignal
given to the Task or Op that started it.
Callbacks passed to map, chain, and tap must not throw. A throw rejects the internal promise,
which has no rejection handler, so the returned value stays pending instead of surfacing an error.
Deferred.all resolves only after every input resolves and keeps the input order. One input that
never resolves keeps the combined value pending. Deferred.to.Promise inherits the same rule: a
value that never resolves produces a promise that never settles.