Skip to content

Commit ec3bbdf

Browse files
committed
fix: diffing async computed and promise inside signal
1 parent fbf25e5 commit ec3bbdf

File tree

2 files changed

+36
-32
lines changed

2 files changed

+36
-32
lines changed

packages/qwik/src/core/client/vnode-diff.ts

Lines changed: 30 additions & 31 deletions
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,7 @@ import { JSXNodeImpl, isJSXNode } from '../shared/jsx/jsx-node';
1414
import { Fragment, type Props } from '../shared/jsx/jsx-runtime';
1515
import { directGetPropsProxyProp, type PropsProxy } from '../shared/jsx/props-proxy';
1616
import { Slot } from '../shared/jsx/slot.public';
17-
import type { JSXNodeInternal, JSXOutput } from '../shared/jsx/types/jsx-node';
17+
import type { JSXNodeInternal } from '../shared/jsx/types/jsx-node';
1818
import type { JSXChildren } from '../shared/jsx/types/jsx-qwik-attributes';
1919
import { SSRComment, SSRRaw, SkipRender } from '../shared/jsx/utils.public';
2020
import type { QRLInternal } from '../shared/qrl/qrl-class';
@@ -39,7 +39,6 @@ import {
3939
QBackRefs,
4040
QContainerAttr,
4141
QDefaultSlot,
42-
QScopedStyle,
4342
QSlot,
4443
QTemplate,
4544
Q_PREFIX,
@@ -85,7 +84,7 @@ import { getAttributeNamespace, getNewElementNamespaceData } from './vnode-names
8584

8685
export const vnode_diff = (
8786
container: ClientContainer,
88-
jsxNode: JSXOutput,
87+
jsxNode: JSXChildren,
8988
vStartNode: VNode,
9089
scopedStyleIdPrefix: string | null
9190
) => {
@@ -99,8 +98,7 @@ export const vnode_diff = (
9998
*/
10099
const stack: any[] = [];
101100

102-
const asyncQueue: Array<VNode | ValueOrPromise<JSXOutput> | Promise<JSXOutput | JSXChildren>> =
103-
[];
101+
const asyncQueue: Array<VNode | ValueOrPromise<JSXChildren> | Promise<JSXChildren>> = [];
104102

105103
////////////////////////////////
106104
//// Traverse state variables
@@ -152,7 +150,7 @@ export const vnode_diff = (
152150
//////////////////////////////////////////////
153151
//////////////////////////////////////////////
154152

155-
function diff(jsxNode: JSXOutput, vStartNode: VNode) {
153+
function diff(jsxNode: JSXChildren, vStartNode: VNode) {
156154
assertFalse(vnode_isVNode(jsxNode), 'JSXNode should not be a VNode');
157155
assertTrue(vnode_isVNode(vStartNode), 'vStartNode should be a VNode');
158156
vParent = vStartNode as ElementVNode | VirtualVNode;
@@ -185,15 +183,8 @@ export const vnode_diff = (
185183
if (currentSignal !== unwrappedSignal) {
186184
const vHost = (vNewNode || vCurrent)!;
187185
descend(
188-
resolveSignalAndDescend(
189-
retryOnPromise(() =>
190-
trackSignalAndAssignHost(
191-
unwrappedSignal,
192-
vHost,
193-
EffectProperty.VNODE,
194-
container
195-
)
196-
)
186+
resolveSignalAndDescend(() =>
187+
trackSignalAndAssignHost(unwrappedSignal, vHost, EffectProperty.VNODE, container)
197188
),
198189
true
199190
);
@@ -252,12 +243,19 @@ export const vnode_diff = (
252243
}
253244
}
254245

255-
function resolveSignalAndDescend(value: any) {
256-
if (isPromise(value)) {
257-
asyncQueue.push(value, vNewNode || vCurrent, null);
258-
return null;
246+
function resolveSignalAndDescend(fn: () => ValueOrPromise<any>): ValueOrPromise<any> {
247+
try {
248+
return fn();
249+
} catch (e) {
250+
// Signal threw a promise (async computed signal) - handle retry and async queue
251+
if (isPromise(e)) {
252+
// The thrown promise will resolve when the signal is ready, then retry fn() with retry logic
253+
const retryPromise = e.then(() => retryOnPromise(fn));
254+
asyncQueue.push(retryPromise, vNewNode || vCurrent, null);
255+
return null;
256+
}
257+
throw e;
259258
}
260-
return value;
261259
}
262260

263261
function advance() {
@@ -544,29 +542,30 @@ export const vnode_diff = (
544542

545543
function drainAsyncQueue(): ValueOrPromise<void> {
546544
while (asyncQueue.length) {
547-
const jsxNode = asyncQueue.shift() as ValueOrPromise<JSXNodeInternal>;
545+
let jsxNode = asyncQueue.shift() as ValueOrPromise<JSXChildren>;
548546
const vHostNode = asyncQueue.shift() as VNode;
549547
const styleScopedId = asyncQueue.shift() as string | null;
548+
549+
const diffNode = (jsxNode: JSXChildren, vHostNode: VNode, styleScopedId: string | null) => {
550+
if (styleScopedId) {
551+
vnode_diff(container, jsxNode, vHostNode, addComponentStylePrefix(styleScopedId));
552+
} else {
553+
diff(jsxNode, vHostNode);
554+
}
555+
};
556+
550557
if (isPromise(jsxNode)) {
551558
return jsxNode
552559
.then((jsxNode) => {
553-
if (styleScopedId) {
554-
vnode_diff(container, jsxNode, vHostNode, addComponentStylePrefix(styleScopedId));
555-
} else {
556-
diff(jsxNode, vHostNode);
557-
}
560+
diffNode(jsxNode, vHostNode, styleScopedId);
558561
return drainAsyncQueue();
559562
})
560563
.catch((e) => {
561564
container.handleError(e, vHostNode);
562565
return drainAsyncQueue();
563566
});
564567
} else {
565-
if (styleScopedId) {
566-
vnode_diff(container, jsxNode, vHostNode, addComponentStylePrefix(styleScopedId));
567-
} else {
568-
diff(jsxNode, vHostNode);
569-
}
568+
diffNode(jsxNode, vHostNode, styleScopedId);
570569
}
571570
}
572571
}

packages/qwik/src/core/tests/use-task.spec.tsx

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@ import {
22
Fragment as Component,
33
Fragment,
44
Fragment as Signal,
5+
Fragment as Awaited,
56
Slot,
67
component$,
78
isServer,
@@ -598,7 +599,11 @@ describe.each([
598599
expect(vNode).toMatchVDOM(
599600
<Component>
600601
<p>
601-
Should have a number: "<Fragment>3</Fragment>"
602+
Should have a number: "
603+
<Signal>
604+
<Awaited>3</Awaited>
605+
</Signal>
606+
"
602607
</p>
603608
</Component>
604609
);

0 commit comments

Comments
 (0)