Skip to content

Commit 1d7ae37

Browse files
committed
fix(async): some edge cases
1 parent 3d0b0fb commit 1d7ae37

File tree

5 files changed

+78
-31
lines changed

5 files changed

+78
-31
lines changed

packages/qwik/src/core/reactive-primitives/impl/async-signal-impl.ts

Lines changed: 6 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -168,7 +168,7 @@ export class AsyncSignalImpl<T> extends ComputedSignalImpl<T, AsyncQRL<T>> imple
168168
override async invalidate() {
169169
this.$flags$ |= SignalFlags.INVALID;
170170
this.$clearNextPoll$();
171-
if (this.$effects$?.size) {
171+
if (this.$effects$?.size || this.$loadingEffects$?.size || this.$errorEffects$?.size) {
172172
// compute in next microtask
173173
await true;
174174
this.$computeIfNeeded$();
@@ -265,15 +265,14 @@ export class AsyncSignalImpl<T> extends ComputedSignalImpl<T, AsyncQRL<T>> imple
265265
}
266266

267267
if (isCurrent()) {
268-
this.untrackedLoading = false;
269-
270268
clearTimeout(this.$computationTimeoutId$);
271269

272270
if (this.$flags$ & SignalFlags.INVALID) {
273271
DEBUG && log('Computation finished but signal is invalid, re-running');
274272
// we became invalid again while running, so we need to re-run the computation to get the new promise
275273
this.$computeIfNeeded$();
276274
} else {
275+
this.untrackedLoading = false;
277276
this.$scheduleNextPoll$();
278277
}
279278
}
@@ -292,7 +291,10 @@ export class AsyncSignalImpl<T> extends ComputedSignalImpl<T, AsyncQRL<T>> imple
292291
get untrackedValue() {
293292
this.$computeIfNeeded$();
294293
if (this.$current$?.$promise$) {
295-
if (this.$untrackedValue$ === NEEDS_COMPUTATION) {
294+
if (
295+
this.$untrackedValue$ === NEEDS_COMPUTATION ||
296+
(import.meta.env.TEST ? isServerPlatform() : isServer)
297+
) {
296298
DEBUG && log('Throwing promise while computing initial value', this);
297299
throw this.$current$?.$promise$;
298300
}

packages/qwik/src/core/shared/serdes/inflate.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -155,6 +155,7 @@ export const inflate = (
155155
Error,
156156
unknown?,
157157
number?,
158+
number?,
158159
];
159160
asyncSignal.$computeQrl$ = d[0];
160161
asyncSignal[_EFFECT_BACK_REF] = d[1];
@@ -172,6 +173,7 @@ export const inflate = (
172173
}
173174
// Note, we use the setter so that it schedules polling if needed
174175
asyncSignal.pollMs = d[7] ?? 0;
176+
asyncSignal.$concurrency$ = d[8] ?? 1;
175177
break;
176178
}
177179
// Inflating a SerializerSignal is the same as inflating a ComputedSignal

packages/qwik/src/core/shared/serdes/serdes.unit.ts

Lines changed: 43 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -699,6 +699,14 @@ describe('shared-serialization', () => {
699699
),
700700
{ pollMs: 100 }
701701
);
702+
const concurrent = createAsyncSignal(
703+
inlinedQrl(
704+
({ track }) => Promise.resolve(track(() => (foo as SignalImpl).value) + 1),
705+
'concurrent',
706+
[foo]
707+
),
708+
{ concurrency: 23 }
709+
);
702710

703711
await retryOnPromise(() => {
704712
// note that this won't subscribe because we're not setting up the context
@@ -707,21 +715,21 @@ describe('shared-serialization', () => {
707715
expect(always.value).toBe(2);
708716
});
709717

710-
const objs = await serialize(dirty, clean, never, always, polling);
718+
const objs = await serialize(dirty, clean, never, always, polling, concurrent);
711719
expect(_dumpState(objs)).toMatchInlineSnapshot(`
712720
"
713721
0 AsyncSignal [
714-
QRL "6#7#5"
722+
QRL "7#8#6"
715723
]
716724
1 AsyncSignal [
717-
QRL "6#8#5"
725+
QRL "7#9#6"
718726
Map [
719727
{string} ":"
720728
EffectSubscription [
721729
RootRef 1
722730
{string} ":"
723731
Set [
724-
RootRef 5
732+
RootRef 6
725733
]
726734
Constant null
727735
]
@@ -733,28 +741,28 @@ describe('shared-serialization', () => {
733741
{number} 2
734742
]
735743
2 AsyncSignal [
736-
QRL "6#9#5"
744+
QRL "7#10#6"
737745
Map [
738746
{string} ":"
739747
EffectSubscription [
740748
RootRef 2
741749
{string} ":"
742750
Set [
743-
RootRef 5
751+
RootRef 6
744752
]
745753
Constant null
746754
]
747755
]
748756
]
749757
3 AsyncSignal [
750-
QRL "6#10#5"
758+
QRL "7#11#6"
751759
Map [
752760
{string} ":"
753761
EffectSubscription [
754762
RootRef 3
755763
{string} ":"
756764
Set [
757-
RootRef 5
765+
RootRef 6
758766
]
759767
Constant null
760768
]
@@ -766,7 +774,7 @@ describe('shared-serialization', () => {
766774
{number} 2
767775
]
768776
4 AsyncSignal [
769-
QRL "6#11#5"
777+
QRL "7#12#6"
770778
Constant undefined
771779
Constant undefined
772780
Constant undefined
@@ -775,22 +783,34 @@ describe('shared-serialization', () => {
775783
Constant NEEDS_COMPUTATION
776784
{number} 100
777785
]
778-
5 Signal [
786+
5 AsyncSignal [
787+
QRL "7#13#6"
788+
Constant undefined
789+
Constant undefined
790+
Constant undefined
791+
Constant undefined
792+
Constant undefined
793+
Constant NEEDS_COMPUTATION
794+
Constant undefined
795+
{number} 23
796+
]
797+
6 Signal [
779798
{number} 1
780-
RootRef 12
781-
RootRef 13
782799
RootRef 14
783-
]
784-
6 {string} "mock-chunk"
785-
7 {string} "dirty"
786-
8 {string} "clean"
787-
9 {string} "never"
788-
10 {string} "always"
789-
11 {string} "polling"
790-
12 RootRef "1 1 1"
791-
13 RootRef "2 1 1"
792-
14 RootRef "3 1 1"
793-
(387 chars)"
800+
RootRef 15
801+
RootRef 16
802+
]
803+
7 {string} "mock-chunk"
804+
8 {string} "dirty"
805+
9 {string} "clean"
806+
10 {string} "never"
807+
11 {string} "always"
808+
12 {string} "polling"
809+
13 {string} "concurrent"
810+
14 RootRef "1 1 1"
811+
15 RootRef "2 1 1"
812+
16 RootRef "3 1 1"
813+
(452 chars)"
794814
`);
795815
});
796816
it(title(TypeIds.Store), async () => {

packages/qwik/src/core/shared/serdes/serialize.ts

Lines changed: 10 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -424,7 +424,12 @@ export async function serialize(serializationContext: SerializationContext): Pro
424424
value.$flags$ & SerializationSignalFlags.SERIALIZATION_STRATEGY_NEVER;
425425
const isInvalid = value.$flags$ & SignalFlags.INVALID;
426426
const isSkippable = fastSkipSerialize(value.$untrackedValue$);
427-
const pollMs = value instanceof AsyncSignalImpl ? value.$pollMs$ : 0;
427+
const pollMs =
428+
value instanceof AsyncSignalImpl && value.$pollMs$ > 0 ? value.$pollMs$ : undefined;
429+
const concurrency =
430+
value instanceof AsyncSignalImpl && value.$concurrency$ !== 1
431+
? value.$concurrency$
432+
: undefined;
428433

429434
if (isInvalid || isSkippable) {
430435
v = NEEDS_COMPUTATION;
@@ -447,10 +452,10 @@ export async function serialize(serializationContext: SerializationContext): Pro
447452

448453
let keepUndefined = false;
449454

450-
if (v !== NEEDS_COMPUTATION || pollMs) {
455+
if (v !== NEEDS_COMPUTATION || pollMs || concurrency !== undefined) {
451456
out.push(v);
452457

453-
if (!isAsync && v === undefined) {
458+
if (v === undefined) {
454459
/**
455460
* If value is undefined, we need to keep it in the output. If we don't do that, later
456461
* during resuming, the value will be set to symbol(invalid) with flag invalid, and
@@ -459,8 +464,9 @@ export async function serialize(serializationContext: SerializationContext): Pro
459464
keepUndefined = true;
460465
}
461466
}
462-
if (pollMs) {
467+
if (isAsync) {
463468
out.push(pollMs);
469+
out.push(concurrency);
464470
}
465471
output(isAsync ? TypeIds.AsyncSignal : TypeIds.ComputedSignal, out, keepUndefined);
466472
} else {

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

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@ import {
1010
useErrorBoundary,
1111
useSignal,
1212
useTask$,
13+
type AsyncSignal,
1314
} from '@qwik.dev/core';
1415
import { domRender, ssrRenderToDom, trigger, waitForDrain } from '@qwik.dev/core/testing';
1516
import { describe, expect, it } from 'vitest';
@@ -303,6 +304,22 @@ describe.each([
303304
</>
304305
);
305306
});
307+
it('should not show initial value after SSR', async () => {
308+
const ref = {} as { s: AsyncSignal<number> };
309+
const Cmp = component$(() => {
310+
const asyncValue = useAsync$(async () => 42, { initial: 10 });
311+
ref.s = asyncValue;
312+
return <div>{asyncValue.value}</div>;
313+
});
314+
const { vNode } = await render(<Cmp />, { debug });
315+
expect(vNode).toMatchVDOM(
316+
<>
317+
<div>
318+
<Signal>{'42'}</Signal>
319+
</div>
320+
</>
321+
);
322+
});
306323
});
307324

308325
describe('error', () => {

0 commit comments

Comments
 (0)