Skip to content
Open
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
6 changes: 6 additions & 0 deletions .changeset/rich-peas-invite.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
---
'@qwik.dev/core': minor
---

FEAT: `useAsync$()` now has `pollMs`, which re-runs the compute function on intervals. You can change signal.pollMs to enable/disable it, and if you set it during SSR it will automatically resume to do the polling.
This way, you can auto-update data on the client without needing to set up timers or events. For example, you can show a "time ago" string that updates every minute, or you can poll an API for updates, and change the poll interval when the window goes idle.
8 changes: 4 additions & 4 deletions packages/docs/src/routes/api/qwik/api.json
Original file line number Diff line number Diff line change
Expand Up @@ -244,7 +244,7 @@
}
],
"kind": "Interface",
"content": "```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\n[error](#)\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.\n\n\n</td></tr>\n<tr><td>\n\n[loading](#)\n\n\n</td><td>\n\n\n</td><td>\n\nboolean\n\n\n</td><td>\n\nWhether the signal is currently 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[promise()](#asyncsignal-promise)\n\n\n</td><td>\n\nA promise that resolves when the value is computed.\n\n\n</td></tr>\n</tbody></table>",
"content": "```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\n[error](#)\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.\n\n\n</td></tr>\n<tr><td>\n\n[loading](#)\n\n\n</td><td>\n\n\n</td><td>\n\nboolean\n\n\n</td><td>\n\nWhether the signal is currently loading.\n\n\n</td></tr>\n<tr><td>\n\n[pollMs](#)\n\n\n</td><td>\n\n\n</td><td>\n\nnumber\n\n\n</td><td>\n\nPoll interval in ms. Writable and immediately effective when the signal has consumers. If set to `0`<!-- -->, polling stops.\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[promise()](#asyncsignal-promise)\n\n\n</td><td>\n\nA promise that resolves when the value is computed.\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 Down Expand Up @@ -446,7 +446,7 @@
}
],
"kind": "Function",
"content": "Create an async computed signal which is calculated from the given QRL. A computed signal is a signal which is calculated from other signals or async operation. When the signals change, the computed signal is recalculated.\n\nThe QRL must be a function which returns the value of the signal. The function must not have side effects, and it can be async.\n\n\n```typescript\ncreateAsync$: <T>(qrl: () => Promise<T>, options?: ComputedOptions) => AsyncSignal<T>\n```\n\n\n<table><thead><tr><th>\n\nParameter\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\nqrl\n\n\n</td><td>\n\n() =&gt; Promise&lt;T&gt;\n\n\n</td><td>\n\n\n</td></tr>\n<tr><td>\n\noptions\n\n\n</td><td>\n\n[ComputedOptions](#computedoptions)\n\n\n</td><td>\n\n_(Optional)_\n\n\n</td></tr>\n</tbody></table>\n\n**Returns:**\n\n[AsyncSignal](#asyncsignal)<!-- -->&lt;T&gt;",
"content": "Create a signal holding a `.value` which is calculated from the given async function (QRL). The standalone version of `useAsync$`<!-- -->.\n\n\n```typescript\ncreateAsync$: <T>(qrl: () => Promise<T>, options?: AsyncSignalOptions<T>) => AsyncSignal<T>\n```\n\n\n<table><thead><tr><th>\n\nParameter\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\nqrl\n\n\n</td><td>\n\n() =&gt; Promise&lt;T&gt;\n\n\n</td><td>\n\n\n</td></tr>\n<tr><td>\n\noptions\n\n\n</td><td>\n\nAsyncSignalOptions&lt;T&gt;\n\n\n</td><td>\n\n_(Optional)_\n\n\n</td></tr>\n</tbody></table>\n\n**Returns:**\n\n[AsyncSignal](#asyncsignal)<!-- -->&lt;T&gt;",
"editUrl": "https://github.com/QwikDev/qwik/tree/main/packages/qwik/src/core/reactive-primitives/signal.public.ts",
"mdFile": "core.createasync_.md"
},
Expand All @@ -460,7 +460,7 @@
}
],
"kind": "Function",
"content": "Create a computed signal which is calculated from the given QRL. A computed signal is a signal which is calculated from other signals. When the signals change, the computed signal is recalculated.\n\nThe QRL must be a function which returns the value of the signal. The function must not have side effects, and it must be synchronous.\n\nIf you need the function to be async, use `useAsync$` instead.\n\n\n```typescript\ncreateComputed$: <T>(qrl: () => T, options?: ComputedOptions) => ComputedReturnType<T>\n```\n\n\n<table><thead><tr><th>\n\nParameter\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\nqrl\n\n\n</td><td>\n\n() =&gt; T\n\n\n</td><td>\n\n\n</td></tr>\n<tr><td>\n\noptions\n\n\n</td><td>\n\n[ComputedOptions](#computedoptions)\n\n\n</td><td>\n\n_(Optional)_\n\n\n</td></tr>\n</tbody></table>\n\n**Returns:**\n\n[ComputedReturnType](#computedreturntype)<!-- -->&lt;T&gt;",
"content": "Create a computed signal which is calculated from the given QRL. A computed signal is a signal which is calculated from other signals. When the signals change, the computed signal is recalculated.\n\nThe QRL must be a function which returns the value of the signal. The function must not have side effects, and it must be synchronous.\n\nIf you need the function to be async, use `createAsync$` instead (don't forget to use `track()`<!-- -->).\n\n\n```typescript\ncreateComputed$: <T>(qrl: () => T, options?: ComputedOptions) => ComputedReturnType<T>\n```\n\n\n<table><thead><tr><th>\n\nParameter\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\nqrl\n\n\n</td><td>\n\n() =&gt; T\n\n\n</td><td>\n\n\n</td></tr>\n<tr><td>\n\noptions\n\n\n</td><td>\n\n[ComputedOptions](#computedoptions)\n\n\n</td><td>\n\n_(Optional)_\n\n\n</td></tr>\n</tbody></table>\n\n**Returns:**\n\n[ComputedReturnType](#computedreturntype)<!-- -->&lt;T&gt;",
"editUrl": "https://github.com/QwikDev/qwik/tree/main/packages/qwik/src/core/reactive-primitives/signal.public.ts",
"mdFile": "core.createcomputed_.md"
},
Expand Down Expand Up @@ -2346,7 +2346,7 @@
}
],
"kind": "Function",
"content": "Creates an AsyncSignal which 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\n\n```typescript\nuseAsync$: <T>(qrl: AsyncFn<T>, options?: ComputedOptions | undefined) => AsyncSignal<T>\n```\n\n\n<table><thead><tr><th>\n\nParameter\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\nqrl\n\n\n</td><td>\n\n[AsyncFn](#asyncfn)<!-- -->&lt;T&gt;\n\n\n</td><td>\n\n\n</td></tr>\n<tr><td>\n\noptions\n\n\n</td><td>\n\n[ComputedOptions](#computedoptions) \\| undefined\n\n\n</td><td>\n\n_(Optional)_\n\n\n</td></tr>\n</tbody></table>\n\n**Returns:**\n\n[AsyncSignal](#asyncsignal)<!-- -->&lt;T&gt;",
"content": "Creates an AsyncSignal which 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\n\n```typescript\nuseAsync$: <T>(qrl: AsyncFn<T>, options?: AsyncSignalOptions<T> | undefined) => AsyncSignal<T>\n```\n\n\n<table><thead><tr><th>\n\nParameter\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\nqrl\n\n\n</td><td>\n\n[AsyncFn](#asyncfn)<!-- -->&lt;T&gt;\n\n\n</td><td>\n\n\n</td></tr>\n<tr><td>\n\noptions\n\n\n</td><td>\n\nAsyncSignalOptions&lt;T&gt; \\| undefined\n\n\n</td><td>\n\n_(Optional)_\n\n\n</td></tr>\n</tbody></table>\n\n**Returns:**\n\n[AsyncSignal](#asyncsignal)<!-- -->&lt;T&gt;",
"editUrl": "https://github.com/QwikDev/qwik/tree/main/packages/qwik/src/core/use/use-async.ts",
"mdFile": "core.useasync_.md"
},
Expand Down
29 changes: 21 additions & 8 deletions packages/docs/src/routes/api/qwik/index.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -182,6 +182,21 @@ boolean

Whether the signal is currently loading.

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

[pollMs](#)

</td><td>

</td><td>

number

</td><td>

Poll interval in ms. Writable and immediately effective when the signal has consumers. If set to `0`, polling stops.

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

Expand Down Expand Up @@ -819,12 +834,10 @@ Description

## createAsync$

Create an async computed signal which is calculated from the given QRL. A computed signal is a signal which is calculated from other signals or async operation. When the signals change, the computed signal is recalculated.

The QRL must be a function which returns the value of the signal. The function must not have side effects, and it can be async.
Create a signal holding a `.value` which is calculated from the given async function (QRL). The standalone version of `useAsync$`.

```typescript
createAsync$: <T>(qrl: () => Promise<T>, options?: ComputedOptions) =>
createAsync$: <T>(qrl: () => Promise<T>, options?: AsyncSignalOptions<T>) =>
AsyncSignal<T>;
```

Expand Down Expand Up @@ -858,7 +871,7 @@ options

</td><td>

[ComputedOptions](#computedoptions)
AsyncSignalOptions&lt;T&gt;

</td><td>

Expand All @@ -879,7 +892,7 @@ Create a computed signal which is calculated from the given QRL. A computed sign

The QRL must be a function which returns the value of the signal. The function must not have side effects, and it must be synchronous.

If you need the function to be async, use `useAsync$` instead.
If you need the function to be async, use `createAsync$` instead (don't forget to use `track()`).

```typescript
createComputed$: <T>(qrl: () => T, options?: ComputedOptions) =>
Expand Down Expand Up @@ -9012,7 +9025,7 @@ If the value has not yet been resolved, reading the AsyncSignal will throw a Pro
If 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.

```typescript
useAsync$: <T>(qrl: AsyncFn<T>, options?: ComputedOptions | undefined) =>
useAsync$: <T>(qrl: AsyncFn<T>, options?: AsyncSignalOptions<T> | undefined) =>
AsyncSignal<T>;
```

Expand Down Expand Up @@ -9046,7 +9059,7 @@ options

</td><td>

[ComputedOptions](#computedoptions) \| undefined
AsyncSignalOptions&lt;T&gt; \| undefined

</td><td>

Expand Down
6 changes: 5 additions & 1 deletion packages/docs/src/routes/docs/(qwik)/core/state/index.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -269,7 +269,11 @@ You can use this to instantiate a custom class.

### `useAsync$()`

`useAsync$()` is similar to `useComputed$()`, but it allows the computed function to be asynchronous. It returns a signal that has the result of the async function.If you read it before the async function has completed, it will stop execution and re-run the reading function when the async function is resolved.
`useAsync$()` is similar to `useComputed$()`, but it allows the compute function to be asynchronous. It returns a signal that has the result of the async function. The common use case is to fetch data asynchronously, possibly based on other signals (you need to read those with `track()`).

If you read the `.value` before the async function has completed, it will throw a `Promise` for the result, which causes Qwik to wait until that resolves and then re-run the reading function.

You can pass the `pollMs` option to have the async function re-execute periodically. This is useful to keep data fresh.

### `useResource$()`

Expand Down
2 changes: 1 addition & 1 deletion packages/qwik/handlers.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -6,4 +6,4 @@
*
* Make sure that these handlers are listed in manifest.ts
*/
export { _chk, _run, _task, _val } from '@qwik.dev/core';
export { _chk, _res, _run, _task, _val } from '@qwik.dev/core';
4 changes: 3 additions & 1 deletion packages/qwik/src/core/internal.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
export { _noopQrl, _noopQrlDEV, _regSymbol } from './shared/qrl/qrl';
export type { QRLInternal as _QRLInternal } from './shared/qrl/qrl-class';
export { createQRL as _createQRL } from './shared/qrl/qrl-class';
export { qrlToString as _qrlToString } from './shared/serdes/qrl-to-string';
// ^ keep this above to avoid circular dependency issues

export {
Expand Down Expand Up @@ -41,7 +43,7 @@ export {
isStringifiable as _isStringifiable,
type Stringifiable as _Stringifiable,
} from './shared-types';
export { _chk, _val } from './shared/jsx/bind-handlers';
export { _chk, _res, _val } from './shared/jsx/bind-handlers';
export { _jsxC, _jsxQ, _jsxS, _jsxSorted, _jsxSplit } from './shared/jsx/jsx-internal';
export { isJSXNode as _isJSXNode } from './shared/jsx/jsx-node';
export { _getConstProps, _getVarProps } from './shared/jsx/props-proxy';
Expand Down
25 changes: 21 additions & 4 deletions packages/qwik/src/core/qwik.core.api.md
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@ export type AsyncFn<T> = (ctx: AsyncCtx) => Promise<T>;
export interface AsyncSignal<T = unknown> extends ComputedSignal<T> {
error: Error | undefined;
loading: boolean;
pollMs: number;
promise(): Promise<T>;
}

Expand Down Expand Up @@ -189,14 +190,16 @@ export interface CorrectedToggleEvent extends Event {
readonly prevState: 'open' | 'closed';
}

// Warning: (ae-forgotten-export) The symbol "AsyncSignalOptions" needs to be exported by the entry point index.d.ts
//
// @public
export const createAsync$: <T>(qrl: () => Promise<T>, options?: ComputedOptions) => AsyncSignal<T>;
export const createAsync$: <T>(qrl: () => Promise<T>, options?: AsyncSignalOptions<T>) => AsyncSignal<T>;

// Warning: (ae-forgotten-export) The symbol "AsyncSignalImpl" needs to be exported by the entry point index.d.ts
// Warning: (ae-internal-missing-underscore) The name "createAsyncQrl" should be prefixed with an underscore because the declaration is marked as @internal
//
// @internal (undocumented)
export const createAsyncQrl: <T>(qrl: QRL<(ctx: AsyncCtx) => Promise<T>>, options?: ComputedOptions) => AsyncSignalImpl<T>;
export const createAsyncQrl: <T>(qrl: QRL<(ctx: AsyncCtx) => Promise<T>>, options?: AsyncSignalOptions<T>) => AsyncSignalImpl<T>;

// @public
export const createComputed$: <T>(qrl: () => T, options?: ComputedOptions) => ComputedReturnType<T>;
Expand All @@ -210,6 +213,9 @@ export const createComputedQrl: <T>(qrl: QRL<() => T>, options?: ComputedOptions
// @public
export const createContextId: <STATE = unknown>(name: string) => ContextId<STATE>;

// @internal
export const _createQRL: <TYPE>(chunk: string | null, symbol: string, symbolRef?: null | ValueOrPromise<TYPE>, symbolFn?: null | (() => Promise<Record<string, TYPE>>), captures?: Readonly<unknown[]> | string | null) => _QRLInternal<TYPE>;

// Warning: (ae-forgotten-export) The symbol "SerializerArg" needs to be exported by the entry point index.d.ts
// Warning: (ae-forgotten-export) The symbol "SerializerSignal" needs to be exported by the entry point index.d.ts
//
Expand Down Expand Up @@ -714,6 +720,14 @@ export type _QRLInternal<TYPE = unknown> = QRL<TYPE> & QRLInternalMethods<TYPE>;
// @internal
export const _qrlSync: <TYPE extends Function>(fn: TYPE, serializedFn?: string) => SyncQRL<TYPE>;

// Warning: (ae-forgotten-export) The symbol "SyncQRLInternal" needs to be exported by the entry point index.d.ts
//
// @internal (undocumented)
export function _qrlToString(serializationContext: SerializationContext, qrl: _QRLInternal | SyncQRLInternal): string;

// @internal (undocumented)
export function _qrlToString(serializationContext: SerializationContext, qrl: _QRLInternal | SyncQRLInternal, raw: true): [string, string, string | null];

// @public @deprecated (undocumented)
export type QwikAnimationEvent<T = Element> = NativeAnimationEvent;

Expand Down Expand Up @@ -883,6 +897,9 @@ export interface RenderSSROptions {
stream: StreamWriter;
}

// @internal
export function _res(this: string | undefined, _: any, element: Element): void;

// @internal (undocumented)
export const _resolveContextWithoutSequentialScope: <STATE>(context: ContextId<STATE>) => STATE | undefined;

Expand Down Expand Up @@ -1737,12 +1754,12 @@ export const untrack: <T, A extends any[]>(expr: ((...args: A) => T) | Signal<T>
export const unwrapStore: <T>(value: T) => T;

// @public
export const useAsync$: <T>(qrl: AsyncFn<T>, options?: ComputedOptions | undefined) => AsyncSignal<T>;
export const useAsync$: <T>(qrl: AsyncFn<T>, options?: AsyncSignalOptions<T> | undefined) => AsyncSignal<T>;

// Warning: (ae-internal-missing-underscore) The name "useAsyncQrl" should be prefixed with an underscore because the declaration is marked as @internal
//
// @internal (undocumented)
export const useAsyncQrl: <T>(qrl: QRL<AsyncFn<T>>, options?: ComputedOptions) => AsyncSignal<T>;
export const useAsyncQrl: <T>(qrl: QRL<AsyncFn<T>>, options?: AsyncSignalOptions<T>) => AsyncSignal<T>;

// @public
export const useComputed$: <T>(qrl: ComputedFn<T>, options?: ComputedOptions | undefined) => ComputedReturnType<T>;
Expand Down
Loading