diff --git a/packages/runtime-core/__tests__/scheduler.spec.ts b/packages/runtime-core/__tests__/scheduler.spec.ts index cf961667e68..3e454aec023 100644 --- a/packages/runtime-core/__tests__/scheduler.spec.ts +++ b/packages/runtime-core/__tests__/scheduler.spec.ts @@ -441,6 +441,29 @@ describe('scheduler', () => { await nextTick() expect(calls).toEqual(['job1', 'job2', 'cb1', 'cb2']) }) + + test('jobs added during post flush are ordered correctly', async () => { + const calls: string[] = [] + + const job1: SchedulerJob = () => { + calls.push('job1') + } + job1.id = 1 + + const job2: SchedulerJob = () => { + calls.push('job2') + } + job2.id = 2 + + queuePostFlushCb(() => { + queueJob(job2) + queueJob(job1) + }) + + await nextTick() + + expect(calls).toEqual(['job1', 'job2']) + }) }) test('sort job based on id', async () => { @@ -758,6 +781,37 @@ describe('scheduler', () => { expect(spy).toHaveBeenCalledTimes(1) }) + test('flushPreFlushCbs inside a post job', async () => { + const calls: string[] = [] + const callsAfterFlush: string[] = [] + + const job1: SchedulerJob = () => { + calls.push('job1') + } + job1.id = 1 + job1.flags! |= SchedulerJobFlags.PRE + + const job2: SchedulerJob = () => { + calls.push('job2') + } + job2.id = 2 + job2.flags! |= SchedulerJobFlags.PRE + + queuePostFlushCb(() => { + queueJob(job2) + queueJob(job1) + + // e.g. nested app.mount() call + flushPreFlushCbs() + callsAfterFlush.push(...calls) + }) + + await nextTick() + + expect(callsAfterFlush).toEqual(['job1', 'job2']) + expect(calls).toEqual(['job1', 'job2']) + }) + it('nextTick should return promise', async () => { const fn = vi.fn(() => { return 1 diff --git a/packages/runtime-core/src/scheduler.ts b/packages/runtime-core/src/scheduler.ts index 798f850b7de..b40c31d3952 100644 --- a/packages/runtime-core/src/scheduler.ts +++ b/packages/runtime-core/src/scheduler.ts @@ -40,11 +40,8 @@ export interface SchedulerJob extends Function { export type SchedulerJobs = SchedulerJob | SchedulerJob[] -let isFlushing = false -let isFlushPending = false - const queue: SchedulerJob[] = [] -let flushIndex = 0 +let flushIndex = -1 const pendingPostFlushCbs: SchedulerJob[] = [] let activePostFlushCbs: SchedulerJob[] | null = null @@ -74,7 +71,7 @@ export function nextTick( // watcher should be inserted immediately before the update job. This allows // watchers to be skipped if the component is unmounted by the parent update. function findInsertionIndex(id: number) { - let start = isFlushing ? flushIndex + 1 : 0 + let start = flushIndex + 1 let end = queue.length while (start < end) { @@ -115,8 +112,7 @@ export function queueJob(job: SchedulerJob): void { } function queueFlush() { - if (!isFlushing && !isFlushPending) { - isFlushPending = true + if (!currentFlushPromise) { currentFlushPromise = resolvedPromise.then(flushJobs) } } @@ -141,8 +137,8 @@ export function queuePostFlushCb(cb: SchedulerJobs): void { export function flushPreFlushCbs( instance?: ComponentInternalInstance, seen?: CountMap, - // if currently flushing, skip the current job itself - i: number = isFlushing ? flushIndex + 1 : 0, + // skip the current job + i: number = flushIndex + 1, ): void { if (__DEV__) { seen = seen || new Map() @@ -211,8 +207,6 @@ const getId = (job: SchedulerJob): number => job.id == null ? (job.flags! & SchedulerJobFlags.PRE ? -1 : Infinity) : job.id function flushJobs(seen?: CountMap) { - isFlushPending = false - isFlushing = true if (__DEV__) { seen = seen || new Map() } @@ -255,15 +249,13 @@ function flushJobs(seen?: CountMap) { } } - flushIndex = 0 + flushIndex = -1 queue.length = 0 flushPostFlushCbs(seen) - isFlushing = false currentFlushPromise = null - // some postFlushCb queued jobs! - // keep flushing until it drains. + // If new jobs have been added to either queue, keep flushing if (queue.length || pendingPostFlushCbs.length) { flushJobs(seen) }