Преглед на файлове

fix(scheduler): job ordering when the post queue is flushing (#12090)

skirtle преди 1 година
родител
ревизия
577edca8e7
променени са 2 файла, в които са добавени 61 реда и са изтрити 15 реда
  1. 54 0
      packages/runtime-core/__tests__/scheduler.spec.ts
  2. 7 15
      packages/runtime-core/src/scheduler.ts

+ 54 - 0
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

+ 7 - 15
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<T = void, R = void>(
 // 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)
     }