Skip to content

Commit bddac05

Browse files
committed
fix(scheduler): drain after promise + refactor
1 parent 19d621e commit bddac05

File tree

2 files changed

+102
-76
lines changed

2 files changed

+102
-76
lines changed

packages/qwik/src/core/shared/scheduler.ts

Lines changed: 102 additions & 75 deletions
Original file line numberDiff line numberDiff line change
@@ -80,15 +80,7 @@
8080
* declaration order within component.
8181
*/
8282

83-
import { assertEqual } from './error/assert';
84-
import type { QRLInternal } from './qrl/qrl-class';
85-
import type { JSXOutput } from './jsx/types/jsx-node';
86-
import { Task, TaskFlags, cleanupTask, runTask, type TaskFn } from '../use/use-task';
87-
import { runResource, type ResourceDescriptor } from '../use/use-resource';
88-
import { logWarn } from './utils/log';
89-
import { isPromise, maybeThenPassError, retryOnPromise, safeCall } from './utils/promises';
90-
import type { ValueOrPromise } from './utils/types';
91-
import { isDomContainer } from '../client/dom-container';
83+
import { isDomContainer, type DomContainer } from '../client/dom-container';
9284
import {
9385
ElementVNodeProps,
9486
VNodeFlags,
@@ -97,21 +89,28 @@ import {
9789
type ElementVNode,
9890
type VirtualVNode,
9991
} from '../client/types';
100-
import { vnode_isVNode, vnode_setAttr, VNodeJournalOpCode } from '../client/vnode';
92+
import { VNodeJournalOpCode, vnode_isVNode, vnode_setAttr } from '../client/vnode';
10193
import { vnode_diff } from '../client/vnode-diff';
102-
import { executeComponent } from './component-execution';
103-
import type { Container, HostElement } from './types';
94+
import { triggerEffects, type ComputedSignal, type WrappedSignal } from '../signal/signal';
10495
import { isSignal, type Signal } from '../signal/signal.public';
105-
import { type DomContainer } from '../client/dom-container';
106-
import { serializeAttribute } from './utils/styles';
96+
import type { TargetType } from '../signal/store';
97+
import type { ISsrNode } from '../ssr/ssr-types';
98+
import { runResource, type ResourceDescriptor } from '../use/use-resource';
99+
import { Task, TaskFlags, cleanupTask, runTask, type TaskFn } from '../use/use-task';
100+
import { executeComponent } from './component-execution';
107101
import type { OnRenderFn } from './component.public';
102+
import { assertEqual } from './error/assert';
108103
import type { Props } from './jsx/jsx-runtime';
104+
import type { JSXOutput } from './jsx/types/jsx-node';
105+
import type { QRLInternal } from './qrl/qrl-class';
106+
import { ssrNodeDocumentPosition, vnode_documentPosition } from './scheduler-document-position';
107+
import type { Container, HostElement } from './types';
108+
import { logWarn } from './utils/log';
109109
import { QScopedStyle } from './utils/markers';
110+
import { isPromise, retryOnPromise, safeCall } from './utils/promises';
110111
import { addComponentStylePrefix } from './utils/scoped-styles';
111-
import { type WrappedSignal, type ComputedSignal, triggerEffects } from '../signal/signal';
112-
import type { TargetType } from '../signal/store';
113-
import { ssrNodeDocumentPosition, vnode_documentPosition } from './scheduler-document-position';
114-
import type { ISsrNode } from '../ssr/ssr-types';
112+
import { serializeAttribute } from './utils/styles';
113+
import type { ValueOrPromise } from './utils/types';
115114

116115
// Turn this on to get debug output of what the scheduler is doing.
117116
const DEBUG: boolean = false;
@@ -150,7 +149,7 @@ export interface Chore {
150149
$target$: ChoreTarget | null;
151150
$payload$: unknown;
152151
$resolve$: (value: any) => void;
153-
$promise$: Promise<any>;
152+
$promise$?: Promise<any>;
154153
$returnValue$: any;
155154
$executed$: boolean;
156155
}
@@ -168,6 +167,10 @@ export type Scheduler = ReturnType<typeof createScheduler>;
168167

169168
type ChoreTarget = HostElement | QRLInternal<(...args: unknown[]) => unknown> | Signal | TargetType;
170169

170+
const getPromise = (chore: Chore) => {
171+
return (chore.$promise$ ||= new Promise((resolve) => (chore.$resolve$ = resolve)));
172+
};
173+
171174
export const createScheduler = (
172175
container: Container,
173176
scheduleDrain: () => void,
@@ -273,7 +276,6 @@ export const createScheduler = (
273276
$returnValue$: null,
274277
$executed$: false,
275278
};
276-
chore.$promise$ = new Promise((resolve) => (chore.$resolve$ = resolve));
277279
DEBUG && debugTrace('schedule', chore, currentChore, choreQueue);
278280
chore = sortedInsert(choreQueue, chore, (container as DomContainer).rootVNode || null);
279281
if (!journalFlushScheduled && runLater) {
@@ -283,7 +285,7 @@ export const createScheduler = (
283285
scheduleDrain();
284286
}
285287
if (runLater) {
286-
return chore.$promise$;
288+
return getPromise(chore);
287289
} else {
288290
return drainUpTo(chore, (container as DomContainer).rootVNode || null);
289291
}
@@ -301,7 +303,7 @@ export const createScheduler = (
301303
}
302304
if (currentChore) {
303305
// Already running chore, which means we're waiting for async completion
304-
return runUptoChore.$promise$;
306+
return getPromise(currentChore).then(() => drainUpTo(runUptoChore, rootVNode));
305307
}
306308
while (choreQueue.length) {
307309
const nextChore = choreQueue[0];
@@ -327,9 +329,10 @@ export const createScheduler = (
327329
continue;
328330
}
329331
const returnValue = executeChore(nextChore);
330-
if (isPromise(returnValue)) {
331-
const promise = returnValue.then(() => drainUpTo(runUptoChore, rootVNode));
332-
return promise;
332+
if (currentChore) {
333+
// We're waiting for the returnValue promise
334+
// Continue draining after it is resolved
335+
return returnValue.then(() => drainUpTo(runUptoChore, rootVNode));
333336
}
334337
}
335338
return runUptoChore.$returnValue$;
@@ -376,12 +379,18 @@ export const createScheduler = (
376379
);
377380
break;
378381
case ChoreType.RESOURCE:
379-
// Don't await the return value of the resource, because async resources should not be awaited.
380-
// The reason for this is that we should be able to update for example a node with loading
381-
// text. If we await the resource, the loading text will not be displayed until the resource
382-
// is loaded.
383-
const result = runResource(chore.$payload$ as ResourceDescriptor<TaskFn>, container, host);
384-
returnValue = isDomContainer(container) ? null : result;
382+
{
383+
// Don't await the return value of the resource, because async resources should not be awaited.
384+
// The reason for this is that we should be able to update for example a node with loading
385+
// text. If we await the resource, the loading text will not be displayed until the resource
386+
// is loaded.
387+
const result = runResource(
388+
chore.$payload$ as ResourceDescriptor<TaskFn>,
389+
container,
390+
host
391+
);
392+
returnValue = isDomContainer(container) ? null : result;
393+
}
385394
break;
386395
case ChoreType.RUN_QRL:
387396
{
@@ -396,66 +405,84 @@ export const createScheduler = (
396405
returnValue = runTask(chore.$payload$ as Task<TaskFn, TaskFn>, container, host);
397406
break;
398407
case ChoreType.CLEANUP_VISIBLE:
399-
const task = chore.$payload$ as Task<TaskFn, TaskFn>;
400-
cleanupTask(task);
408+
{
409+
const task = chore.$payload$ as Task<TaskFn, TaskFn>;
410+
cleanupTask(task);
411+
}
401412
break;
402413
case ChoreType.NODE_DIFF:
403-
const parentVirtualNode = chore.$target$ as VirtualVNode;
404-
let jsx = chore.$payload$ as JSXOutput;
405-
if (isSignal(jsx)) {
406-
jsx = jsx.value as any;
414+
{
415+
const parentVirtualNode = chore.$target$ as VirtualVNode;
416+
let jsx = chore.$payload$ as JSXOutput;
417+
if (isSignal(jsx)) {
418+
jsx = jsx.value as any;
419+
}
420+
returnValue = retryOnPromise(() =>
421+
vnode_diff(container as DomContainer, jsx, parentVirtualNode, null)
422+
);
407423
}
408-
returnValue = retryOnPromise(() =>
409-
vnode_diff(container as DomContainer, jsx, parentVirtualNode, null)
410-
);
411424
break;
412425
case ChoreType.NODE_PROP:
413-
const virtualNode = chore.$host$ as unknown as ElementVNode;
414-
const payload = chore.$payload$ as NodePropPayload;
415-
let value: Signal<any> | string = payload.$value$;
416-
if (isSignal(value)) {
417-
value = value.value as any;
418-
}
419-
const isConst = payload.$isConst$;
420-
const journal = (container as DomContainer).$journal$;
421-
const property = chore.$idx$ as string;
422-
const serializedValue = serializeAttribute(property, value, payload.$scopedStyleIdPrefix$);
423-
if (isConst) {
424-
const element = virtualNode[ElementVNodeProps.element] as Element;
425-
journal.push(VNodeJournalOpCode.SetAttribute, element, property, serializedValue);
426-
} else {
427-
vnode_setAttr(journal, virtualNode, property, serializedValue);
426+
{
427+
const virtualNode = chore.$host$ as unknown as ElementVNode;
428+
const payload = chore.$payload$ as NodePropPayload;
429+
let value: Signal<any> | string = payload.$value$;
430+
if (isSignal(value)) {
431+
value = value.value as any;
432+
}
433+
const isConst = payload.$isConst$;
434+
const journal = (container as DomContainer).$journal$;
435+
const property = chore.$idx$ as string;
436+
const serializedValue = serializeAttribute(
437+
property,
438+
value,
439+
payload.$scopedStyleIdPrefix$
440+
);
441+
if (isConst) {
442+
const element = virtualNode[ElementVNodeProps.element] as Element;
443+
journal.push(VNodeJournalOpCode.SetAttribute, element, property, serializedValue);
444+
} else {
445+
vnode_setAttr(journal, virtualNode, property, serializedValue);
446+
}
428447
}
429448
break;
430449
case ChoreType.QRL_RESOLVE: {
431-
const target = chore.$target$ as QRLInternal<any>;
432-
returnValue = !target.resolved ? target.resolve() : null;
450+
{
451+
const target = chore.$target$ as QRLInternal<any>;
452+
returnValue = !target.resolved ? target.resolve() : null;
453+
}
433454
break;
434455
}
435456
case ChoreType.RECOMPUTE_AND_SCHEDULE_EFFECTS: {
436-
const target = chore.$target$ as ComputedSignal<unknown> | WrappedSignal<unknown>;
437-
const forceRunEffects = target.$forceRunEffects$;
438-
target.$forceRunEffects$ = false;
439-
if (!target.$effects$?.length) {
440-
break;
441-
}
442-
returnValue = retryOnPromise(() => {
443-
if (target.$computeIfNeeded$() || forceRunEffects) {
444-
triggerEffects(container, target, target.$effects$);
457+
{
458+
const target = chore.$target$ as ComputedSignal<unknown> | WrappedSignal<unknown>;
459+
const forceRunEffects = target.$forceRunEffects$;
460+
target.$forceRunEffects$ = false;
461+
if (!target.$effects$?.length) {
462+
break;
445463
}
446-
});
464+
returnValue = retryOnPromise(() => {
465+
if (target.$computeIfNeeded$() || forceRunEffects) {
466+
triggerEffects(container, target, target.$effects$);
467+
}
468+
});
469+
}
447470
break;
448471
}
449472
}
450-
return maybeThenPassError(returnValue, (value) => {
451-
if (currentChore) {
452-
currentChore.$executed$ = true;
453-
currentChore.$resolve$?.(value);
454-
}
455-
DEBUG && debugTrace('execute.DONE', null, currentChore, choreQueue);
473+
const after = (value: any) => {
456474
currentChore = null;
457-
return (chore.$returnValue$ = value);
458-
});
475+
chore.$executed$ = true;
476+
chore.$returnValue$ = value;
477+
DEBUG && debugTrace('execute.DONE', null, chore, choreQueue);
478+
chore.$resolve$?.(value);
479+
return value;
480+
};
481+
if (isPromise(returnValue)) {
482+
chore.$promise$ = returnValue;
483+
return returnValue.then(after);
484+
}
485+
return after(returnValue);
459486
}
460487
};
461488

starters/apps/e2e/src/components/computed/computed.tsx

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -26,7 +26,6 @@ export const ComputedBasic = component$(() => {
2626
const triple = useComputed$(() => plus3.value * 3);
2727
const sum = useComputed$(() => double.value + plus3.value + triple.value);
2828

29-
console.log("here");
3029
return (
3130
<div>
3231
<div class="result">count: {count.value}</div>

0 commit comments

Comments
 (0)