Skip to content

RFC: Replace useAsyncComputed$ with a more flexible useAsync$ primitive #324

@wmertens

Description

@wmertens

Champion

@Varixo and @wmertens

What's the motivation for this proposal?

Summary
Replace useAsyncComputed$ with a new useAsync$ that unifies async computation, observable values, polling, and eager cleanup, while aligning closer to useSignal semantics.

Motivation
Current useAsyncComputed$ overlaps with useResource$ but neither are great.
A more powerful, writable async primitive would better support data fetching, streaming, auto-updating values, and background calculations.

Proposal

Rename useAsyncComputed$ to useAsync$:

useAsync$<T>(
  callback: (ctx: { track: TrackFn, cleanup: CleanupFn, poll: PollFn }) => Promise<T> | T,
  options?: {
    initial?: T | (() => T);    // like useSignal; prevents throw on read when uninitialized
    eagerCleanup?: boolean;     // run cleanup next tick when subscribers drop to 0
    awaitPrevious?: boolean;    // wait for previous invocation to complete before running again
    pollMs?: number;            // re-run after N ms if subscribers exist
    timeout?: number;           // abort after timeout
  }
): AsyncSignal<unknown>;

Key changes:

  1. Writable resultresult.value = … updates subscribers directly; clears loading/error state.
  2. Error handlingresult.error = … propagates error on read.
  3. Initial value – optional initial avoids throw when reading before first resolution.
  4. Eager cleanup – cleanup runs immediately (next tick) when no subscribers remain.
  5. Polling – via pollMs: number option or poll(ms) callback for periodic re-execution; use .invalidate() as before to do it manually.

Example

const data = useAsync$(({ cleanup }) => {
  const channel = await makeChannel();
  channel.on('data', d => data.value = d);     // push updates
  channel.on('error', e => data.error = e);    // propagate errors
  cleanup(() => channel.close());
  return 'PENDING';
}, { eagerCleanup: true });

Benefits

  • Single primitive for async data, observables, polling, and background tasks.
  • Consistent with useSignal mental model.
  • Reduces need for separate useResource$ or custom observables.
  • Enables streaming/server-sent events, WebSocket handling, and auto-refetching patterns out of the box.

Discussion points

  • Should poll be an option, callback, both, or neither?

Addition: generators

The callback should also be able to be a generator function, it should be wrapped so that every invocation updates the value.

Metadata

Metadata

Assignees

No one assigned

    Labels

    Type

    No type

    Projects

    Status

    Released as Stable (STAGE 5)

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions