Deferred — Infallible Async Values
In JavaScript, Promise<A> is the universal container for any asynchronous value. While highly
convenient, a Promise is structurally over-specified for operations that are infallible —
computations that are guaranteed to resolve successfully, such as reading an in-memory cache,
applying a pure transformation, or looking up config defaults.
By exposing .catch() and .finally(), a Promise always implies that rejection is a possibility.
For infallible computations, this is a design mismatch. It forces callers to either ignore the
theoretical possibility of failure or write unnecessary, dead error-handling code that will never be
executed.
Deferred<A> solves this mismatch. It represents an asynchronous value that will eventually resolve
to an A, but it is structurally incapable of rejecting:
Two deliberate design choices make Deferred work:
- Nominal Safety: The
[_deferred]property is a phantom unique symbol. It carries the type parameterAnominally, ensuring that only genuine values produced byDeferredsatisfy the type. A plain, raw{ then: ... }object cannot bypass the type check. - No Chaining or Rejection: The
.then()method accepts only a single fulfillment callback. It returnsvoidrather than a new thenable. There is no second parameter to pass a rejection handler, and no chainable return value. Rejection and chaining are excluded by construction.
Wrapping Promises with fromPromise
Section titled “Wrapping Promises with fromPromise”Deferred.fromPromise is the gateway constructor. It wraps a standard Promise that you are
confident will never reject, lifting it into the infallible Deferred type:
When you call fromPromise, you are asserting to the compiler that the underlying Promise is
infallible. If the Promise does reject, that rejection behaves exactly like an unhandled Promise
rejection at runtime. Only wrap Promises that are guaranteed to succeed, such as those that have
already resolved their errors using defaults or fallback strategies.
Awaiting a Deferred
Section titled “Awaiting a Deferred”Because the JavaScript runtime evaluates await by looking for any object with a compatible
.then() method, Deferred qualifies as a standard thenable. The runtime calls .then(resolve) on
it internally, making await behave identically to awaiting a standard Promise:
TypeScript understands this protocol and infers the correct type A from any Deferred<A>
automatically.
Interoperability: toPromise
Section titled “Interoperability: toPromise”If you need to pass a Deferred value to an external library or a third-party API that strictly
checks instanceof Promise rather than accepting generic thenables, you can convert it using
toPromise:
The resulting Promise is guaranteed to resolve, inheriting the structural infallibility of the
original Deferred.
When to use Deferred
Section titled “When to use Deferred”Use Deferred when:
Section titled “Use Deferred when:”- The async work cannot fail: You want the function signature to explicitly document that a task is infallible, preventing callers from writing dead error-handling branches.
- You want to restrict chaining: You want to pass an async value to a consumer and structurally
prevent them from attaching
.catch()or.then()chains.
Keep using standard Promises when:
Section titled “Keep using standard Promises when:”- The task can genuinely fail: The operation could time out or lose connection, and you want to
let the Promise reject or model it using a
Resulttype. - You require chaining: You need to chain multiple
.then()or.finally()blocks directly on the async handle.