a reactive (signals based) hypermedia web framework (wip) stormlightlabs.github.io/volt/
hypermedia frontend signals
1# Async Effects 2 3Volt’s `asyncEffect` helper runs asynchronous workflows whenever one or more signals change. 4It handles abort signals, debounce/throttle scheduling, retries, and cleanup so you can focus on data fetching logic instead of wiring. 5 6## When to use it 7 8- Fetching or mutating remote data in response to signal changes. 9- Performing background work that should cancel when inputs flip rapidly. 10- Retrying transient failures without duplicating boilerplate. 11- Triggering imperative side effects (e.g., analytics) that return cleanups. 12 13## Basic Example 14 15In this example, if you change `query` with `query.set("new value")` the effect re-runs. 16 17```ts 18import { asyncEffect, signal } from "voltx.js"; 19 20const query = signal(""); 21const results = signal([]); 22 23asyncEffect(async () => { 24 if (!query.get()) { 25 results.set([]); 26 return; 27 } 28 29 const response = await fetch(`/api/search?q=${encodeURIComponent(query.get())}`); 30 results.set(await response.json()); 31}, [query]); 32``` 33 34If the effect returns a cleanup function it is invoked before the next execution and on disposal. 35 36## Abortable Fetches 37 38Pass `{ abortable: true }` to receive an `AbortSignal`. 39VoltX aborts the previous run each time dependencies change or when you dispose the effect. 40 41```ts 42asyncEffect( 43 async (signal) => { 44 const response = await fetch(`/api/files/${fileId.get()}`, { signal }); 45 data.set(await response.json()); 46 }, 47 [fileId], 48 { abortable: true }, 49); 50``` 51 52## Debounce and Throttle 53 54- `debounce: number` waits until inputs are quiet for the specified milliseconds. 55- `throttle: number` skips executions until the interval has elapsed; the latest change runs once the window closes. 56 57```ts 58asyncEffect( 59 async () => { 60 await saveDraft(documentId.get(), draftBody.get()); 61 }, 62 [draftBody], 63 { debounce: 500 }, 64); 65``` 66 67Combine `debounce` and `abortable` to cancel in-flight saves when the user keeps typing. 68 69## Retry Strategies 70 71`retries` controls how many times VoltX should re-run the effect after it throws. 72`retryDelay` adds a pause between attempts. Use `onError` for custom logging or to expose a manual `retry()` hook. 73 74```ts 75asyncEffect( 76 async () => { 77 const res = await fetch("/api/profile"); 78 if (!res.ok) throw new Error("Request failed"); 79 profile.set(await res.json()); 80 }, 81 [refreshToken], 82 { 83 retries: 3, 84 retryDelay: 1000, 85 onError(error, retry) { 86 toast.error(error.message); 87 retry(); // optionally kick off another attempt immediately 88 }, 89 }, 90); 91``` 92 93## Cleanup and disposal 94 95Hold on to the disposer returned by `asyncEffect` when you need to stop reacting: 96 97```ts 98const stop = asyncEffect(async () => { 99 const subscription = await openStream(); 100 return () => subscription.close(); 101}, [channel]); 102 103window.addEventListener("beforeunload", stop); 104``` 105 106VoltX automatically runs the cleanup when dependencies change, when the effect retries successfully, and when you call the disposer. 107 108## Tips 109 110- Keep the dependency list stable & wrap derived values in computeds if necessary. 111- Throw errors from the effect body to trigger retries or the `onError` callback. 112- Prefer `debounce` for text inputs and `throttle` for scroll/resize signals. 113- Always check abort signals before committing expensive results when `abortable` is enabled.