Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 4 additions & 4 deletions e2e/qwik-e2e/tests/resource.e2e.ts
Original file line number Diff line number Diff line change
Expand Up @@ -108,28 +108,28 @@ test.describe('resource serialization', () => {

await expect(button1).toHaveText('PASS: Success 0');
await expect(button2).toHaveText('ERROR: Error: failed 0');
await expect(button3).toHaveText('ERROR: Error: timeout 0');
await expect(button3).toHaveText('ERROR: Error: timeout 100ms 0');

// Click button 1
await button1.click();

await expect(button1).toHaveText('PASS: Success 1');
await expect(button2).toHaveText('ERROR: Error: failed 0');
await expect(button3).toHaveText('ERROR: Error: timeout 0');
await expect(button3).toHaveText('ERROR: Error: timeout 100ms 0');

// Click button 2
await button2.click();

await expect(button1).toHaveText('PASS: Success 1');
await expect(button2).toHaveText('ERROR: Error: failed 1');
await expect(button3).toHaveText('ERROR: Error: timeout 1');
await expect(button3).toHaveText('ERROR: Error: timeout 100ms 1');

// Click button 2
await button2.click();

await expect(button1).toHaveText('PASS: Success 1');
await expect(button2).toHaveText('ERROR: Error: failed 2');
await expect(button3).toHaveText('ERROR: Error: timeout 2');
await expect(button3).toHaveText('ERROR: Error: timeout 100ms 2');
});

test('issue 2014 - resource signal resumability', async ({ page }) => {
Expand Down
4 changes: 2 additions & 2 deletions packages/docs/src/routes/api/qwik/api.json
Original file line number Diff line number Diff line change
Expand Up @@ -261,7 +261,7 @@
}
],
"kind": "Interface",
"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.\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</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>",
"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>",
"editUrl": "https://github.com/QwikDev/qwik/tree/main/packages/qwik/src/core/reactive-primitives/signal.public.ts",
"mdFile": "core.asyncsignal.md"
},
Expand All @@ -275,7 +275,7 @@
}
],
"kind": "Interface",
"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.\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\nDefaults to `true`<!-- -->.\n\n\n</td></tr>\n<tr><td>\n\nawaitPrevious?\n\n\n</td><td>\n\n\n</td><td>\n\nboolean\n\n\n</td><td>\n\n_(Optional)_ Wait for previous invocation to complete before running again.\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. Consumers can check staleness and manually trigger recomputation. - \\*\\*`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>",
"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>",
"editUrl": "https://github.com/QwikDev/qwik/tree/main/packages/qwik/src/core/reactive-primitives/types.ts",
"mdFile": "core.asyncsignaloptions.md"
},
Expand Down
57 changes: 39 additions & 18 deletions packages/docs/src/routes/api/qwik/index.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -221,7 +221,7 @@ Error \| undefined

</td><td>

The error that occurred while computing the signal, if any. This will be cleared when the signal is successfully computed.
The 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.

</td></tr>
<tr><td>
Expand Down Expand Up @@ -265,6 +265,40 @@ signal.loading ? (
);
```

</td></tr>
<tr><td>

untrackedError

</td><td>

</td><td>

Error \| undefined

</td><td>

Lets you read the error state without subscribing to `.error` updates. It does not trigger lazy loading of the signal.

Setting it will trigger listeners for `.error`.

</td></tr>
<tr><td>

untrackedLoading

</td><td>

</td><td>

boolean

</td><td>

Lets you read the loading state without subscribing to `.loading` updates. It also triggers lazy loading of the signal.

Setting it will trigger listeners for `.loading`.

</td></tr>
</tbody></table>

Expand Down Expand Up @@ -345,26 +379,13 @@ boolean

</td><td>

_(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.
_(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.

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.

Defaults to `true`.

</td></tr>
<tr><td>

awaitPrevious?

</td><td>

</td><td>

boolean

</td><td>
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.

_(Optional)_ Wait for previous invocation to complete before running again.
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).

Defaults to `true`.

Expand Down Expand Up @@ -449,7 +470,7 @@ number

_(Optional)_ Controls staleness and polling behavior.

- \*\*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. Consumers can check staleness and manually trigger recomputation. - \*\*`0`\*\* (default): No staleness tracking or polling.
- \*\*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.

</td></tr>
<tr><td>
Expand Down
Loading
Loading