Skip to content

Commit 9963f0a

Browse files
authored
Merge pull request #8552 from QwikDev/async-expires
refactor(Async): interval => expires+poll
2 parents c9e3222 + bb7d3ea commit 9963f0a

File tree

18 files changed

+285
-109
lines changed

18 files changed

+285
-109
lines changed

.changeset/social-rooms-invite.md

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
---
2+
'@qwik.dev/core': major
3+
---
4+
5+
BREAKING (beta only): the `interval` option of `useAsync$` has been renamed to `expires`, and a new `poll` option has been added to control whether the async function should be automatically re-run when it expires.

packages/docs/src/routes/api/qwik/api.json

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -261,7 +261,7 @@
261261
}
262262
],
263263
"kind": "Interface",
264-
"content": "An AsyncSignal holds the result of the given async function. If the function uses `track()` to track reactive state, and that state changes, the AsyncSignal is recalculated, and if the result changed, all tasks which are tracking the AsyncSignal will be re-run and all subscribers (components, tasks etc) that read the AsyncSignal will be updated.\n\nIf the async function throws an error, the AsyncSignal will capture the error and set the `error` property. The error can be cleared by re-running the async function successfully.\n\nWhile the async function is running, the `.loading` property will be set to `true`<!-- -->. Once the function completes, `loading` will be set to `false`<!-- -->.\n\nIf the value has not yet been resolved, reading the AsyncSignal will throw a Promise, which will retry the component or task once the value resolves.\n\nIf the value has been resolved, but the async function is re-running, reading the AsyncSignal will subscribe to it and return the last resolved value until the new value is ready. As soon as the new value is ready, the subscribers will be updated.\n\nIf the async function threw an error, reading the `.value` will throw that same error. Read from `.error` to check if there was an error.\n\n\n```typescript\nexport interface AsyncSignal<T = unknown> extends ComputedSignal<T> \n```\n**Extends:** [ComputedSignal](#computedsignal)<!-- -->&lt;T&gt;\n\n\n<table><thead><tr><th>\n\nProperty\n\n\n</th><th>\n\nModifiers\n\n\n</th><th>\n\nType\n\n\n</th><th>\n\nDescription\n\n\n</th></tr></thead>\n<tbody><tr><td>\n\nerror\n\n\n</td><td>\n\n\n</td><td>\n\nError \\| undefined\n\n\n</td><td>\n\nThe error that occurred while computing the signal, if any. This will be cleared when the signal is successfully computed. It does not trigger lazy loading of the signal.\n\n\n</td></tr>\n<tr><td>\n\ninterval\n\n\n</td><td>\n\n\n</td><td>\n\nnumber\n\n\n</td><td>\n\nStaleness/poll interval in ms. Writable and immediately effective.\n\n- \\*\\*Positive\\*\\*: Poll — re-compute after this many ms when subscribers exist. - \\*\\*Negative\\*\\*: Stale-only — mark stale after `|interval|` ms, no auto-recompute. - \\*\\*`0`<!-- -->\\*\\*: No staleness tracking or polling.\n\n\n</td></tr>\n<tr><td>\n\nloading\n\n\n</td><td>\n\n\n</td><td>\n\nboolean\n\n\n</td><td>\n\nWhether the signal is currently loading. This will trigger lazy loading of the signal, so you can use it like this:\n\n```tsx\nsignal.loading ? <Loading /> : signal.error ? <Error /> : <Component\nvalue={signal.value} />\n```\n\n\n</td></tr>\n<tr><td>\n\nuntrackedError\n\n\n</td><td>\n\n\n</td><td>\n\nError \\| undefined\n\n\n</td><td>\n\nLets you read the error state without subscribing to `.error` updates. It does not trigger lazy loading of the signal.\n\nSetting it will trigger listeners for `.error`<!-- -->.\n\n\n</td></tr>\n<tr><td>\n\nuntrackedLoading\n\n\n</td><td>\n\n\n</td><td>\n\nboolean\n\n\n</td><td>\n\nLets you read the loading state without subscribing to `.loading` updates. It also triggers lazy loading of the signal.\n\nSetting it will trigger listeners for `.loading`<!-- -->.\n\n\n</td></tr>\n</tbody></table>\n\n\n<table><thead><tr><th>\n\nMethod\n\n\n</th><th>\n\nDescription\n\n\n</th></tr></thead>\n<tbody><tr><td>\n\n[abort(reason)](#asyncsignal-abort)\n\n\n</td><td>\n\nAbort the current computation and run cleanups if needed.\n\n\n</td></tr>\n<tr><td>\n\n[invalidate(info)](#asyncsignal-invalidate)\n\n\n</td><td>\n\nUse this to force recalculation. If you pass `info`<!-- -->, it will be provided to the calculation function.\n\n\n</td></tr>\n<tr><td>\n\n[promise()](#asyncsignal-promise)\n\n\n</td><td>\n\nA promise that resolves when the value is computed or rejected.\n\n\n</td></tr>\n</tbody></table>",
264+
"content": "An AsyncSignal holds the result of the given async function. If the function uses `track()` to track reactive state, and that state changes, the AsyncSignal is recalculated, and if the result changed, all tasks which are tracking the AsyncSignal will be re-run and all subscribers (components, tasks etc) that read the AsyncSignal will be updated.\n\nIf the async function throws an error, the AsyncSignal will capture the error and set the `error` property. The error can be cleared by re-running the async function successfully.\n\nWhile the async function is running, the `.loading` property will be set to `true`<!-- -->. Once the function completes, `loading` will be set to `false`<!-- -->.\n\nIf the value has not yet been resolved, reading the AsyncSignal will throw a Promise, which will retry the component or task once the value resolves.\n\nIf the value has been resolved, but the async function is re-running, reading the AsyncSignal will subscribe to it and return the last resolved value until the new value is ready. As soon as the new value is ready, the subscribers will be updated.\n\nIf the async function threw an error, reading the `.value` will throw that same error. Read from `.error` to check if there was an error.\n\n\n```typescript\nexport interface AsyncSignal<T = unknown> extends ComputedSignal<T> \n```\n**Extends:** [ComputedSignal](#computedsignal)<!-- -->&lt;T&gt;\n\n\n<table><thead><tr><th>\n\nProperty\n\n\n</th><th>\n\nModifiers\n\n\n</th><th>\n\nType\n\n\n</th><th>\n\nDescription\n\n\n</th></tr></thead>\n<tbody><tr><td>\n\nerror\n\n\n</td><td>\n\n\n</td><td>\n\nError \\| undefined\n\n\n</td><td>\n\nThe error that occurred while computing the signal, if any. This will be cleared when the signal is successfully computed. It does not trigger lazy loading of the signal.\n\n\n</td></tr>\n<tr><td>\n\nexpires\n\n\n</td><td>\n\n\n</td><td>\n\nnumber\n\n\n</td><td>\n\nExpiration time in ms. Writable and immediately effective.\n\nWhen set, the signal is invalidated after this many ms. Whether it auto-recomputes depends on the `poll` property. `0` means no expiration.\n\n\n</td></tr>\n<tr><td>\n\ninterval\n\n\n</td><td>\n\n\n</td><td>\n\nnumber\n\n\n</td><td>\n\n\n</td></tr>\n<tr><td>\n\nloading\n\n\n</td><td>\n\n\n</td><td>\n\nboolean\n\n\n</td><td>\n\nWhether the signal is currently loading. This will trigger lazy loading of the signal, so you can use it like this:\n\n```tsx\nsignal.loading ? <Loading /> : signal.error ? <Error /> : <Component\nvalue={signal.value} />\n```\n\n\n</td></tr>\n<tr><td>\n\npoll\n\n\n</td><td>\n\n\n</td><td>\n\nboolean\n\n\n</td><td>\n\nWhether to automatically re-run the function when the value expires. Writable and immediately effective. Only relevant when `expires` is set.\n\nDefaults to `true`<!-- -->.\n\n\n</td></tr>\n<tr><td>\n\nuntrackedError\n\n\n</td><td>\n\n\n</td><td>\n\nError \\| undefined\n\n\n</td><td>\n\nLets you read the error state without subscribing to `.error` updates. It does not trigger lazy loading of the signal.\n\nSetting it will trigger listeners for `.error`<!-- -->.\n\n\n</td></tr>\n<tr><td>\n\nuntrackedLoading\n\n\n</td><td>\n\n\n</td><td>\n\nboolean\n\n\n</td><td>\n\nLets you read the loading state without subscribing to `.loading` updates. It also triggers lazy loading of the signal.\n\nSetting it will trigger listeners for `.loading`<!-- -->.\n\n\n</td></tr>\n</tbody></table>\n\n\n<table><thead><tr><th>\n\nMethod\n\n\n</th><th>\n\nDescription\n\n\n</th></tr></thead>\n<tbody><tr><td>\n\n[abort(reason)](#asyncsignal-abort)\n\n\n</td><td>\n\nAbort the current computation and run cleanups if needed.\n\n\n</td></tr>\n<tr><td>\n\n[invalidate(info)](#asyncsignal-invalidate)\n\n\n</td><td>\n\nUse this to force recalculation. If you pass `info`<!-- -->, it will be provided to the calculation function.\n\n\n</td></tr>\n<tr><td>\n\n[promise()](#asyncsignal-promise)\n\n\n</td><td>\n\nA promise that resolves when the value is computed or rejected.\n\n\n</td></tr>\n</tbody></table>",
265265
"editUrl": "https://github.com/QwikDev/qwik/tree/main/packages/qwik/src/core/reactive-primitives/signal.public.ts",
266266
"mdFile": "core.asyncsignal.md"
267267
},
@@ -275,7 +275,7 @@
275275
}
276276
],
277277
"kind": "Interface",
278-
"content": "```typescript\nexport interface AsyncSignalOptions<T> extends ComputedOptions \n```\n**Extends:** [ComputedOptions](#computedoptions)\n\n\n<table><thead><tr><th>\n\nProperty\n\n\n</th><th>\n\nModifiers\n\n\n</th><th>\n\nType\n\n\n</th><th>\n\nDescription\n\n\n</th></tr></thead>\n<tbody><tr><td>\n\nallowStale?\n\n\n</td><td>\n\n\n</td><td>\n\nboolean\n\n\n</td><td>\n\n_(Optional)_ When true (default), the previous value is kept while the signal re-computes after invalidation, so reads return stale data instead of throwing a promise. Reactivity will then update the readers when the new value is ready.\n\nWhen false, invalidation clears the value so reads throw the computation promise (like the initial load), which is useful for navigations where showing old data would be confusing.\n\nNote that `interval` invalidations are not affected by this option and will keep the old value while the new value is loading, to avoid flashing loaders during polling.\n\nThis option only affects manual invalidations via `invalidate()`<!-- -->, and `interval` invalidations where no new value will be loaded (`interval` is negative, or there are no subscribers).\n\nDefaults to `true`<!-- -->.\n\n\n</td></tr>\n<tr><td>\n\nclientOnly?\n\n\n</td><td>\n\n\n</td><td>\n\nboolean\n\n\n</td><td>\n\n_(Optional)_ When true, the async computation is postponed to the browser. On SSR, the signal remains INVALID and does not execute the function. On the client, it will compute on first read.\n\nDefaults to `false`<!-- -->.\n\n\n</td></tr>\n<tr><td>\n\nconcurrency?\n\n\n</td><td>\n\n\n</td><td>\n\nnumber\n\n\n</td><td>\n\n_(Optional)_ Maximum number of concurrent computations. Use `0` for unlimited.\n\nDefaults to `1`<!-- -->.\n\n\n</td></tr>\n<tr><td>\n\neagerCleanup?\n\n\n</td><td>\n\n\n</td><td>\n\nboolean\n\n\n</td><td>\n\n_(Optional)_ When subscribers drop to 0, run cleanup in the next tick, instead of waiting for the function inputs to change.\n\nDefaults to `false`<!-- -->, meaning cleanup happens only when inputs change.\n\n\n</td></tr>\n<tr><td>\n\ninitial?\n\n\n</td><td>\n\n\n</td><td>\n\nT \\| (() =&gt; T)\n\n\n</td><td>\n\n_(Optional)_ Like useSignal's `initial`<!-- -->; prevents the throw on first read when uninitialized\n\n\n</td></tr>\n<tr><td>\n\ninterval?\n\n\n</td><td>\n\n\n</td><td>\n\nnumber\n\n\n</td><td>\n\n_(Optional)_ Controls staleness and polling behavior.\n\n- \\*\\*Positive\\*\\*: Re-run the function after `interval` ms if subscribers exist (polling). - \\*\\*Negative\\*\\*: Mark the value as stale after `|interval|` ms, but do NOT auto-recompute. Recomputation happens when reading `.value` or `.loading`<!-- -->. - \\*\\*`0`<!-- -->\\*\\* (default): No staleness tracking or polling.\n\n\n</td></tr>\n<tr><td>\n\ntimeout?\n\n\n</td><td>\n\n\n</td><td>\n\nnumber\n\n\n</td><td>\n\n_(Optional)_ Maximum time in milliseconds to wait for the async computation to complete. If exceeded, the computation is aborted and an error is thrown.\n\nIf `0`<!-- -->, no timeout is applied.\n\nDefaults to `0`<!-- -->.\n\n\n</td></tr>\n</tbody></table>",
278+
"content": "```typescript\nexport interface AsyncSignalOptions<T> extends ComputedOptions \n```\n**Extends:** [ComputedOptions](#computedoptions)\n\n\n<table><thead><tr><th>\n\nProperty\n\n\n</th><th>\n\nModifiers\n\n\n</th><th>\n\nType\n\n\n</th><th>\n\nDescription\n\n\n</th></tr></thead>\n<tbody><tr><td>\n\nallowStale?\n\n\n</td><td>\n\n\n</td><td>\n\nboolean\n\n\n</td><td>\n\n_(Optional)_ When true (default), the previous value is kept while the signal re-computes after invalidation, so reads return stale data instead of throwing a promise. Reactivity will then update the readers when the new value is ready.\n\nWhen false, invalidation clears the value so reads throw the computation promise (like the initial load), which is useful for navigations where showing old data would be confusing.\n\nNote that polling invalidations (`expires` with `poll: true`<!-- -->) are not affected by this option and will keep the old value while the new value is loading, to avoid flashing loaders.\n\nThis option only affects manual invalidations via `invalidate()`<!-- -->, and non-polling expirations (`poll: false`<!-- -->, or there are no subscribers).\n\nDefaults to `true`<!-- -->.\n\n\n</td></tr>\n<tr><td>\n\nclientOnly?\n\n\n</td><td>\n\n\n</td><td>\n\nboolean\n\n\n</td><td>\n\n_(Optional)_ When true, the async computation is postponed to the browser. On SSR, the signal remains INVALID and does not execute the function. On the client, it will compute on first read.\n\nDefaults to `false`<!-- -->.\n\n\n</td></tr>\n<tr><td>\n\nconcurrency?\n\n\n</td><td>\n\n\n</td><td>\n\nnumber\n\n\n</td><td>\n\n_(Optional)_ Maximum number of concurrent computations. Use `0` for unlimited.\n\nDefaults to `1`<!-- -->.\n\n\n</td></tr>\n<tr><td>\n\neagerCleanup?\n\n\n</td><td>\n\n\n</td><td>\n\nboolean\n\n\n</td><td>\n\n_(Optional)_ When subscribers drop to 0, run cleanup in the next tick, instead of waiting for the function inputs to change.\n\nDefaults to `false`<!-- -->, meaning cleanup happens only when inputs change.\n\n\n</td></tr>\n<tr><td>\n\nexpires?\n\n\n</td><td>\n\n\n</td><td>\n\nnumber\n\n\n</td><td>\n\n_(Optional)_ Time in milliseconds after which the value expires.\n\nWhen the value expires and subscribers exist, the signal is invalidated. If `poll` is `true` (default), the function is re-run automatically. If `poll` is `false`<!-- -->, the value is marked stale and recomputation happens when reading `.value` or `.loading`<!-- -->.\n\n`0` (default) means no expiration.\n\n\n</td></tr>\n<tr><td>\n\ninitial?\n\n\n</td><td>\n\n\n</td><td>\n\nT \\| (() =&gt; T)\n\n\n</td><td>\n\n_(Optional)_ Like useSignal's `initial`<!-- -->; prevents the throw on first read when uninitialized\n\n\n</td></tr>\n<tr><td>\n\ninterval?\n\n\n</td><td>\n\n\n</td><td>\n\nnumber\n\n\n</td><td>\n\n_(Optional)_\n\n\n</td></tr>\n<tr><td>\n\npoll?\n\n\n</td><td>\n\n\n</td><td>\n\nboolean\n\n\n</td><td>\n\n_(Optional)_ Whether to automatically re-run the function when the value expires. Only relevant when `expires` is set.\n\nDefaults to `true`<!-- -->.\n\n\n</td></tr>\n<tr><td>\n\ntimeout?\n\n\n</td><td>\n\n\n</td><td>\n\nnumber\n\n\n</td><td>\n\n_(Optional)_ Maximum time in milliseconds to wait for the async computation to complete. If exceeded, the computation is aborted and an error is thrown.\n\nIf `0`<!-- -->, no timeout is applied.\n\nDefaults to `0`<!-- -->.\n\n\n</td></tr>\n</tbody></table>",
279279
"editUrl": "https://github.com/QwikDev/qwik/tree/main/packages/qwik/src/core/reactive-primitives/types.ts",
280280
"mdFile": "core.asyncsignaloptions.md"
281281
},

packages/docs/src/routes/api/qwik/index.mdx

Lines changed: 71 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -226,7 +226,7 @@ The error that occurred while computing the signal, if any. This will be cleared
226226
</td></tr>
227227
<tr><td>
228228
229-
interval
229+
expires
230230
231231
</td><td>
232232
@@ -236,9 +236,22 @@ number
236236
237237
</td><td>
238238
239-
Staleness/poll interval in ms. Writable and immediately effective.
239+
Expiration time in ms. Writable and immediately effective.
240240
241-
- \*\*Positive\*\*: Poll — re-compute after this many ms when subscribers exist. - \*\*Negative\*\*: Stale-only — mark stale after `|interval|` ms, no auto-recompute. - \*\*`0`\*\*: No staleness tracking or polling.
241+
When set, the signal is invalidated after this many ms. Whether it auto-recomputes depends on the `poll` property. `0` means no expiration.
242+
243+
</td></tr>
244+
<tr><td>
245+
246+
interval
247+
248+
</td><td>
249+
250+
</td><td>
251+
252+
number
253+
254+
</td><td>
242255
243256
</td></tr>
244257
<tr><td>
@@ -268,6 +281,23 @@ signal.loading ? (
268281
</td></tr>
269282
<tr><td>
270283
284+
poll
285+
286+
</td><td>
287+
288+
</td><td>
289+
290+
boolean
291+
292+
</td><td>
293+
294+
Whether to automatically re-run the function when the value expires. Writable and immediately effective. Only relevant when `expires` is set.
295+
296+
Defaults to `true`.
297+
298+
</td></tr>
299+
<tr><td>
300+
271301
untrackedError
272302
273303
</td><td>
@@ -383,9 +413,9 @@ _(Optional)_ When true (default), the previous value is kept while the signal re
383413
384414
When false, invalidation clears the value so reads throw the computation promise (like the initial load), which is useful for navigations where showing old data would be confusing.
385415
386-
Note that `interval` invalidations are not affected by this option and will keep the old value while the new value is loading, to avoid flashing loaders during polling.
416+
Note that polling invalidations (`expires` with `poll: true`) are not affected by this option and will keep the old value while the new value is loading, to avoid flashing loaders.
387417
388-
This option only affects manual invalidations via `invalidate()`, and `interval` invalidations where no new value will be loaded (`interval` is negative, or there are no subscribers).
418+
This option only affects manual invalidations via `invalidate()`, and non-polling expirations (`poll: false`, or there are no subscribers).
389419
390420
Defaults to `true`.
391421
@@ -443,6 +473,25 @@ Defaults to `false`, meaning cleanup happens only when inputs change.
443473
</td></tr>
444474
<tr><td>
445475
476+
expires?
477+
478+
</td><td>
479+
480+
</td><td>
481+
482+
number
483+
484+
</td><td>
485+
486+
_(Optional)_ Time in milliseconds after which the value expires.
487+
488+
When the value expires and subscribers exist, the signal is invalidated. If `poll` is `true` (default), the function is re-run automatically. If `poll` is `false`, the value is marked stale and recomputation happens when reading `.value` or `.loading`.
489+
490+
`0` (default) means no expiration.
491+
492+
</td></tr>
493+
<tr><td>
494+
446495
initial?
447496
448497
</td><td>
@@ -468,9 +517,24 @@ number
468517
469518
</td><td>
470519
471-
_(Optional)_ Controls staleness and polling behavior.
520+
_(Optional)_
472521
473-
- \*\*Positive\*\*: Re-run the function after `interval` ms if subscribers exist (polling). - \*\*Negative\*\*: Mark the value as stale after `|interval|` ms, but do NOT auto-recompute. Recomputation happens when reading `.value` or `.loading`. - \*\*`0`\*\* (default): No staleness tracking or polling.
522+
</td></tr>
523+
<tr><td>
524+
525+
poll?
526+
527+
</td><td>
528+
529+
</td><td>
530+
531+
boolean
532+
533+
</td><td>
534+
535+
_(Optional)_ Whether to automatically re-run the function when the value expires. Only relevant when `expires` is set.
536+
537+
Defaults to `true`.
474538
475539
</td></tr>
476540
<tr><td>

0 commit comments

Comments
 (0)