Overview

asyncAction supports transparent result caching (LRU with optional TTL) and automatic periodic re-fetching. Both features are opt-in via the options object — set cacheLimit / cacheDuration for caching, and pollInterval before calling startPolling().

Use .cache(...args) to invoke with memoization (skips the async function on a cache hit). Use startPolling(...args) / stopPolling() for live dashboards and other “keep fresh” UIs. For advanced composition outside asyncAction, see createCacheManager / createPollingManager.

Built-in vs standalone factories

Approach
When to use
Notes
asyncAction options + methods Common case cacheLimit / cacheDuration / pollInterval plus .cache(), .startPolling(), etc.
createCacheManager / createPollingManager Advanced Compose a custom async primitive outside of asyncAction. Most apps never need these directly.

asyncAction.cache() and startPolling()

cache(1) skips the network on a hit — loadedAt stays put. A plain call(1) always fetches. startPolling(1) / stopPolling() use pollInterval. subscribe() logs each settle.

import { html, atom, asyncAction, nothing, renderApp } from 'mates';

const loadUser = asyncAction(async (id) => {
const res = await fetch(
`https://jsonplaceholder.typicode.com/users/${id}`,
{ signal: loadUser.abortController.signal },
);
if (!res.ok) throw new Error('Not found');
const user = await res.json();
// Stamp happens only on a real run — .cache() hits reuse this object
return { ...user, loadedAt: new Date().toLocaleTimeString() };
}, { cacheLimit: 10, cacheDuration: 30_000, pollInterval: 4000 });

const events = atom([]);
loadUser.subscribe(() => {
events.set((prev) => [
`${loadUser.status()} · ${loadUser.data()?.name ?? loadUser.error()?.message ?? '—'}`,
...prev,
].slice(0, 4));
});

const App = () => {
const polling = atom(false);

return () => html`
<div class="m-col m-gap">
<div class="m-flex m-flex-wrap m-items-center m-justify-center m-gap-sm">
<button class="btn-primary" @click=${() => loadUser.cache(1)} ?disabled=${loadUser.isLoading()}>
.cache(1)
</button>
<button @click=${() => loadUser.cache(2)} ?disabled=${loadUser.isLoading()}>
.cache(2)
</button>
<button @click=${() => loadUser(1)} ?disabled=${loadUser.isLoading()}>
call(1) — always network
</button>