Skip to main content
On this pageFunctions

AsyncData

Functions

Schema

functionsource
/**
 * Builds the six-state `AsyncData` Schema for the given data and error
 *  Schemas (value-first). Put `schema` in your Model; use the returned
 *  constructors when you want ones typed to this instance's `A` and `E`.
 */
<A, AI, E, EI>(
  dataSchema: Codec<A, AI>,
  errorSchema: Codec<E, EI>
): AsyncDataSchema<A, AI, E, EI>

fail

functionsource
/** Bare-value alias for `Failure({ error })`, mirroring `Result.fail`. */
<E, A = never>(error: E): AsyncData<A, E>

getData

functionsource
/**
 * Returns the data as an `Option`, spanning all three data-bearing states:
 *  `Some` for `Success`, `Refreshing`, and `Stale`, `None` otherwise.
 *  Payload-named because it deliberately spans three tags.
 */
<A, E>(self: AsyncData<A, E>): Option<A>

getError

functionsource
/**
 * Returns the error as an `Option`, spanning both error-bearing states:
 *  `Some` for `Failure` and `Stale`, `None` otherwise. Symmetric with
 *  `getData`.
 */
<A, E>(self: AsyncData<A, E>): Option<E>

hasData

functionsource
/**
 * Returns `true` when the state holds data: `Success`, `Refreshing`, or
 *  `Stale`.
 */
<A, E>(self: AsyncData<A, E>): boolean

hasError

functionsource
/**
 * Returns `true` when the state carries an error: `Failure` or `Stale`.
 *  The error-channel twin of `hasData`.
 */
<A, E>(self: AsyncData<A, E>): boolean

isAsyncData

functionsource
/**
 * Type guard on `unknown`: checks that the value has a `_tag` belonging to
 *  the six `AsyncData` states.
 */
(input: unknown): unknown

isFailure

functionsource
/** Returns `true` only for the `Failure` state, not `Stale`. Refinement. */
<A, E>(self: AsyncData<A, E>): unknown

isIdle

functionsource
/** Returns `true` only for the `Idle` state. Refinement. */
<A, E>(self: AsyncData<A, E>): unknown

isLoading

functionsource
/**
 * Returns `true` only for the empty `Loading` state, not `Refreshing`.
 *  Refinement.
 */
<A, E>(self: AsyncData<A, E>): unknown

isPending

functionsource
/**
 * Returns `true` when a request is in flight: `Loading` or `Refreshing`
 *  only. `Stale` is not pending because its fetch already failed. Use for a
 *  spinner regardless of held data.
 */
<A, E>(self: AsyncData<A, E>): boolean

isRefreshing

functionsource
/** Returns `true` only for the `Refreshing` state. Refinement. */
<A, E>(self: AsyncData<A, E>): unknown

isStale

functionsource
/**
 * Returns `true` only for the `Stale` state: a failed refresh holding the
 *  last good data. Refinement.
 */
<A, E>(self: AsyncData<A, E>): unknown

isSuccess

functionsource
/**
 * Returns `true` only for the `Success` state, not `Refreshing` or
 *  `Stale`. Refinement.
 */
<A, E>(self: AsyncData<A, E>): unknown

revalidate

functionsource
/**
 * The loaded-only revalidation transition: `Success` and `Stale` move to
 *  `Refreshing`, every other state yields `None`. Unlike
 *  `revalidateOrLoad` there is no cold-start `Loading`, so only caches
 *  that actually hold data revalidate; this is the generic refresher path
 *  after a mutation.
 */
<A, E>(self: AsyncData<A, E>): Option<AsyncData<A, E>>

revalidateOrLoad

functionsource
/**
 * The revalidate-on-entry transition: revalidates loaded data and loads
 *  cold data. The data-bearing loaded states (`Success`, `Stale`) move to
 *  `Refreshing`; the cold no-data states (`Idle`, `Failure`) start a fresh
 *  `Loading`; the already-pending states (`Loading`, `Refreshing`) yield
 *  `None` so the request in flight is not restarted. `None` means no
 *  transition, and no load Command, is needed.
 */
<A, E>(self: AsyncData<A, E>): Option<AsyncData<A, E>>

succeed

functionsource
/** Bare-value alias for `Success({ data })`, mirroring `Result.succeed`. */
<A, E = never>(data: A): AsyncData<A, E>

Types

AsyncData

typesource
/**
 * The six-state union for an asynchronously loaded value: `Idle | Loading
 *  | Refreshing(data) | Failure(error) | Stale(error, data) | Success(data)`.
 *  Value-first type parameters, matching `Result<A, E>` and `Exit<A, E>`.
 * 
 *  `Refreshing` and `Stale` make stale-while-revalidate and
 *  stale-on-failure first-class states: both hold the previous good `data`,
 *  `Refreshing` while a reload is in flight, `Stale` after a reload failed.
 * 
 *  Data-presence classification, used throughout the module:
 * 
 *  - "has data" states: `Success`, `Refreshing`, `Stale`.
 *  - "no data" states: `Idle`, `Loading`, `Failure`.
 *  - "pending" states (a request in flight): `Loading`, `Refreshing` only.
 *    `Stale` is not pending; its fetch already failed.
 * 
 *  Naming rule: predicates are tag-named (`isSuccess`, `isFailure`) but
 *  getters are payload-named (`getData`, `getError`) because `getData` spans
 *  three tags (`Success`, `Refreshing`, `Stale`) and `getError` spans two
 *  (`Failure`, `Stale`). Handler keys are tag-named where dispatch is per tag
 *  (`match`) and channel-named where one handler covers multiple tags
 *  (`matchData`, `mapBoth`).
 */
type AsyncData = Idle | Loading | Refreshing<A> | Failure<E> | Stale<A, E> | Success<A>

AsyncDataEncoded

typesource
/**
 * The encoded (wire) form of a `AsyncData<A, E>` whose data encodes to
 *  `AI` and whose error encodes to `EI`.
 */
type AsyncDataEncoded = Readonly<{
  _tag: "Idle"
}> | Readonly<{
  _tag: "Loading"
}> | Readonly<{
  _tag: "Refreshing"
  data: AI
}> | Readonly<{
  _tag: "Failure"
  error: EI
}> | Readonly<{
  _tag: "Stale"
  data: AI
  error: EI
}> | Readonly<{
  _tag: "Success"
  data: AI
}>

AsyncDataSchema

typesource
/**
 * What the `Schema` factory returns: the six-state Union codec to embed in
 *  a Model plus the Schema-bound constructors for the four parameterized
 *  states. Combinators are not part of this bag; they are module-level free
 *  functions over any `AsyncData<A, E>` value.
 */
type AsyncDataSchema = Readonly<{
  Failure: (payload: Readonly<{
    error: E
  }>) => AsyncData<A, E>
  Idle: typeof Idle
  Loading: typeof Loading
  Refreshing: (payload: Readonly<{
    data: A
  }>) => AsyncData<A, E>
  schema: S.Codec<AsyncData<A, E>, AsyncDataEncoded<AI, EI>>
  Stale: (payload: Readonly<{
    data: A
    error: E
  }>) => AsyncData<A, E>
  Success: (payload: Readonly<{
    data: A
  }>) => AsyncData<A, E>
}>

Failure

typesource
/** The `Failure` state: request failed, showing the failure. */
type Failure = Readonly<{
  _tag: "Failure"
  error: E
}>

Idle

typesource
/** The `Idle` state: nothing requested yet. */
type Idle = Readonly<{
  _tag: "Idle"
}>

Loading

typesource
/** The `Loading` state: first request in flight, no prior data. */
type Loading = Readonly<{
  _tag: "Loading"
}>

Refreshing

typesource
/** The `Refreshing` state: reloading while holding the previous good data. */
type Refreshing = Readonly<{
  _tag: "Refreshing"
  data: A
}>

Stale

typesource
/**
 * The `Stale` state: the last refresh failed, still holding the previous
 *  good data. The failed-refresh mirror of `Refreshing`.
 */
type Stale = Readonly<{
  _tag: "Stale"
  data: A
  error: E
}>

Success

typesource
/** The `Success` state: request succeeded, data present. */
type Success = Readonly<{
  _tag: "Success"
  data: A
}>

Constants

Failure

constsource
/**
 * Constructs a `Failure` state carrying the error only. Plain value
 *  builder, generic in `E`; use the `Schema` factory's `Failure` for a
 *  Schema-bound constructor.
 */
const Failure: (payload: Readonly<{
  error: E
}>) => AsyncData<never, E>

Idle

constsource
/**
 * Constructs the `Idle` state. A parameter-free callable Schema, so it can
 *  also serve as a Union member.
 */
const Idle: CallableTaggedStruct<"Idle", {}>

Loading

constsource
/**
 * Constructs the `Loading` state. A parameter-free callable Schema, so it
 *  can also serve as a Union member.
 */
const Loading: CallableTaggedStruct<"Loading", {}>

Refreshing

constsource
/**
 * Constructs a `Refreshing` state holding the previous good data. Plain
 *  value builder, generic in `A`; use the `Schema` factory's `Refreshing`
 *  for a Schema-bound constructor.
 */
const Refreshing: (payload: Readonly<{
  data: A
}>) => AsyncData<A, never>

Stale

constsource
/**
 * Constructs a `Stale` state carrying both the refresh error and the
 *  last good data. Plain value builder, generic in `A` and `E`; use the
 *  `Schema` factory's `Stale` for a Schema-bound constructor.
 */
const Stale: (payload: Readonly<{
  data: A
  error: E
}>) => AsyncData<A, E>

Success

constsource
/**
 * Constructs a `Success` state holding the data. Plain value builder,
 *  generic in `A`; use the `Schema` factory's `Success` for a Schema-bound
 *  constructor.
 */
const Success: (payload: Readonly<{
  data: A
}>) => AsyncData<A, never>

all

constsource
/**
 * Combines an iterable or record of values under the `zipWith` lattice.
 *  An iterable collapses to an in-order array of data (empty input yields
 *  `Success({ data: [] })`); a record builds a struct (empty input yields
 *  `Success({ data: {} })`). The highest-ranked no-data state blocks, the
 *  leftmost `Failure` error wins, and any `Stale` in an all-data set makes
 *  the result `Stale` with the leftmost `Stale` error. All inputs must share
 *  one error type; unify with `mapError` first.
 * 
 *  The record form is the multi-resource screen:
 *  `all({ user, orders, prefs })` combines into one value whose data is the
 *  struct of all datas.
 */
const all: (inputs: Inputs) => [Inputs] extends [ReadonlyArray<AsyncData<any, any>>]
  ? AsyncData<{
    [K in keyof Inputs]: [Inputs[K]] extends [AsyncData<infer A, any>]
      ? A
      : never
  }, Inputs[number] extends never
    ? never
    : [Inputs[number]] extends [AsyncData<any, infer E>]
      ? E
      : never>
  : [Inputs] extends [Iterable<AsyncData<infer A, infer E>>]
    ? AsyncData<Array<A>, E>
    : AsyncData<{
      [K in keyof Inputs]: [Inputs[K]] extends [AsyncData<infer A, any>]
        ? A
        : never
    }, Inputs[keyof Inputs] extends never
      ? never
      : [Inputs[keyof Inputs]] extends [AsyncData<any, infer E>]
        ? E
        : never>

flatMap

constsource
/**
 * Treats every data-bearing state (`Success`, `Refreshing`, `Stale`)
 *  exactly like `Success(data)`: returns `f(data)` unchanged, dropping the
 *  tag and any `Stale` error, so a caller can settle in-flight or stale data
 *  into `Success`. The no-data states pass through. Widens the error channel
 *  to `E | E2`, matching `Result.flatMap`.
 */
const flatMap: (f: (data: A) => AsyncData<B, E2>) => (self: AsyncData<A, E>) => AsyncData<B, E2 | E>

getOrElse

constsource
/**
 * Returns the data of any data-bearing state, or `onEmpty()` for the three
 *  no-data states. The fallback is a nullary thunk, mirroring
 *  `Option.getOrElse`, because the no-data states collapse to empty with no
 *  single payload.
 */
const getOrElse: (onEmpty: LazyArg<B>) => (self: AsyncData<A, E>) => B | A

map

constsource
/**
 * Maps the data of all three data-bearing states, preserving each tag so
 *  the `Refreshing` and `Stale` signals survive a pure transform. `Stale`
 *  maps only its `data`, keeping its `error`. The no-data states pass
 *  through unchanged.
 */
const map: (f: (data: A) => B) => (self: AsyncData<A, E>) => AsyncData<B, E>

mapBoth

constsource
/**
 * Maps both channels with channel-named handlers: `onData` spans `Success`,
 *  `Refreshing`, and `Stale`; `onError` spans `Failure` and `Stale`. For
 *  `Stale`, both handlers apply. Tags are preserved.
 */
const mapBoth: (handlers: {
  onData: (data: A) => B
  onError: (error: E) => E2
}) => (self: AsyncData<A, E>) => AsyncData<B, E2>

mapError

constsource
/**
 * Maps the error of the two error-bearing states: `Failure` and `Stale`
 *  transform (`Stale` keeps its `data`), everything else passes through.
 *  Use it to unify heterogeneous error types before a combine.
 */
const mapError: (f: (error: E) => E2) => (self: AsyncData<A, E>) => AsyncData<A, E2>

match

constsource
/**
 * Handles all six states exhaustively, passing each handler its unwrapped
 *  payload. `onStale` alone receives the whole `{ error, data }` payload
 *  object because `Stale` carries two fields. Use `matchData` when the view
 *  does not care which of the data-bearing states it is rendering.
 */
const match: (handlers: Readonly<{
  onFailure: (error: E) => F
  onIdle: Function.LazyArg<B>
  onLoading: Function.LazyArg<C>
  onRefreshing: (data: A) => D
  onStale: (payload: Readonly<{
    data: A
    error: E
  }>) => G
  onSuccess: (data: A) => H
}>) => (self: AsyncData<A, E>) => B | C | D | F | G | H

matchData

constsource
/**
 * Collapses the six states to the three channels a view usually renders:
 *  `onData` spans the data-bearing states (`Success`, `Refreshing`,
 *  `Stale`), `onFailure` receives the `Failure` error, and `onEmpty` covers
 *  `Idle` and `Loading` together. A `Stale` renders through `onData` so its
 *  data stays on screen. Use `matchDataSplit` when `Idle` and `Loading`
 *  render differently, and `match` when the stale error or the `Refreshing`
 *  signal matters.
 */
const matchData: (handlers: {
  onData: (data: A) => D
  onEmpty: Function.LazyArg<B>
  onFailure: (error: E) => C
}) => (self: AsyncData<A, E>) => B | C | D

matchDataSplit

constsource
/**
 * Like `matchData`, but the two cold states are split: `onIdle` handles
 *  `Idle` and `onLoading` handles `Loading`, for views that render nothing
 *  requested yet differently from a request in flight. `onData` and
 *  `onFailure` behave exactly as in `matchData`.
 */
const matchDataSplit: (handlers: {
  onData: (data: A) => F
  onFailure: (error: E) => D
  onIdle: Function.LazyArg<B>
  onLoading: Function.LazyArg<C>
}) => (self: AsyncData<A, E>) => B | C | D | F

orElse

constsource
/**
 * Returns `self` when it holds data (`Success`, `Refreshing`, or `Stale`),
 *  otherwise `that()`. The recovery and cache-fallback combinator: recover
 *  an `Idle`, `Loading`, or `Failure` into a secondary source without
 *  `match`.
 */
const orElse: (that: LazyArg<AsyncData<A, E>>) => (self: AsyncData<A, E>) => AsyncData<A, E>

settle

constsource
/**
 * Folds a settled `Result` into the previous state, keeping the last good
 *  data: a `Result` success becomes `Success`, and a `Result` failure
 *  becomes `Stale({ error, data })` when `self` holds data, else a bare
 *  `Failure`. The primary way to fold a fetch back into the Model, and the
 *  reason a failed refresh keeps data on screen: without `settle`, every
 *  fetch needs a success arm and a failure arm that hand-assemble
 *  `Success`, `Stale`, and `Failure` from the previous state. When a
 *  failure should deliberately drop the previous data, match on the
 *  `Result` directly and build the `Failure` explicitly.
 */
const settle: (result: Result<A, E>) => (self: AsyncData<A, E>) => AsyncData<A, E>

zipWith

constsource
/**
 * Combines two values under the two-tier lattice
 *  `Failure > Loading > Idle > Stale > Refreshing > Success`. If either
 *  input is a no-data state, the highest-ranked such state wins with no
 *  combination (`self`'s error wins when both are `Failure`). Otherwise both
 *  hold data: combine with `f` and tag the result with the highest-ranked
 *  data state present (`self`'s error wins when both are `Stale`).
 * 
 *  Because the combined value needs both inputs' data, a single no-data
 *  input collapses the whole combine to that state:
 *  `zipWith(Idle(), Success({ data }), f)` yields `Idle`.
 */
const zipWith: (that: AsyncData<B, E>, f: (selfData: A, thatData: B) => C) => (self: AsyncData<A, E>) => AsyncData<C, E>

Stay in the update loop.

New releases, patterns, and the occasional deep dive.


Built with Foldkit.

© 2026 Devin Jameson