Core API

createAsync(handler, options?)

Creates an AsyncOperation. The handler receives an AsyncContext followed by the parameters passed to execute.

const operation = createAsync(async ({ signal, requestId }, id: string) => loadUser(id, signal), {
  concurrency: "latest",
  abortable: true,
});

Handler context

FieldTypeDescription
signalAbortSignal | nullPresent when abortable is enabled
requestIdnumberMonotonically increasing operation-local request identifier

Options

OptionDefaultDescription
initialDatanullData used for the initial and reset snapshots
dataOnErrorPreserve dataProduces replacement data after a rejection
concurrency"all"Selects all-request or latest-request state updates
abortablefalseCreates an AbortController for every execution
isEqualAll fieldsDetermines whether a new snapshot is ignored
onSuccess—Runs after an accepted successful state update
onError—Runs after an accepted error state update

Operation

MethodDescription
getSnapshot()Returns the current frozen snapshot
subscribe(listener)Subscribes to accepted snapshot changes and returns an unsubscribe function
execute(...params)Starts the handler and returns its promise
abort()Aborts controllers, invalidates active work, and returns to idle
reset()Invalidates active work and restores initial state

Error behavior

execute() does not consume errors. It updates the snapshot and invokes onError, then rejects with the original error so the caller decides how to handle it.

try {
  await operation.execute();
} catch (error) {
  // Handle the same rejection stored in operation.getSnapshot().error.
}

createRequestLifecycle(options?)

This lower-level helper is for adapters that own their state. It tracks request IDs, concurrency, and optional cancellation without storing data, errors, or loading state. Pass a handler, its parameter tuple, and start, success, and error callbacks to execute. The settled callbacks run only when that request is allowed to update state; success and error receive an isLoading flag. abort() invalidates active requests and returns whether any were active; reset() also starts a new request generation. The caller must update its own state after aborting or resetting.

const lifecycle = createRequestLifecycle({ concurrency: "latest" });
const state = { data: null, error: null, loading: false };

await lifecycle.execute(async (_context, id: string) => loadUser(id), ["42"], {
  start() {
    state.loading = true;
  },
  success(data, isLoading) {
    Object.assign(state, { data, error: null, loading: isLoading });
  },
  error(error, isLoading) {
    Object.assign(state, { error, loading: isLoading });
  },
});