]> git.ipfire.org Git - thirdparty/vuejs/core.git/commitdiff
fix(scheduler): handle queueJob inside postFlushCbs
authorEvan You <yyx990803@gmail.com>
Sun, 28 Oct 2018 16:08:58 +0000 (12:08 -0400)
committerEvan You <yyx990803@gmail.com>
Sun, 28 Oct 2018 16:08:58 +0000 (12:08 -0400)
packages/scheduler/__tests__/scheduler.spec.ts
packages/scheduler/src/index.ts

index a0aa89fd52c40a6a8a717ac17268eb485386726f..74c21daeca0f6d01865d47a789de91a0cfceacaf 100644 (file)
@@ -90,4 +90,27 @@ describe('scheduler', () => {
     await nextTick()
     expect(calls).toEqual(['job1', 'job2'])
   })
+
+  it('queueJob inside postFlushCb', async () => {
+    const calls: any = []
+    const job1 = () => {
+      calls.push('job1')
+    }
+    const cb1 = () => {
+      // queue another job in postFlushCb
+      calls.push('cb1')
+      queueJob(job2, cb2)
+    }
+    const job2 = () => {
+      calls.push('job2')
+    }
+    const cb2 = () => {
+      calls.push('cb2')
+    }
+
+    queueJob(job1, cb1)
+    queueJob(job2, cb2)
+    await nextTick()
+    expect(calls).toEqual(['job1', 'job2', 'cb1', 'cb2', 'job2', 'cb2'])
+  })
 })
index 93172ffdd5c0874020ddebc021b275d71bc323cd..54c8567e7bec76830c3c529fb27d4214ee5d79d9 100644 (file)
@@ -1,33 +1,74 @@
 const queue: Array<() => void> = []
 const postFlushCbs: Array<() => void> = []
+const postFlushCbsForNextTick: Array<() => void> = []
 const p = Promise.resolve()
 
-let hasPendingFlush = false
+let isFlushing = false
+let isFlushingPostCbs = false
 
 export function nextTick(fn?: () => void): Promise<void> {
   return p.then(fn)
 }
 
-export function queueJob(job: () => void, postFlushCb?: () => void) {
+export function queueJob(
+  job: () => void,
+  postFlushCb?: () => void,
+  onError?: (err: Error) => void
+) {
   if (queue.indexOf(job) === -1) {
     queue.push(job)
-    if (!hasPendingFlush) {
-      hasPendingFlush = true
-      nextTick(flushJobs)
+    if (!isFlushing || isFlushingPostCbs) {
+      const p = nextTick(flushJobs)
+      if (onError) p.catch(onError)
     }
   }
-  if (postFlushCb && postFlushCbs.indexOf(postFlushCb) === -1) {
-    postFlushCbs.push(postFlushCb)
+  if (postFlushCb) {
+    if (isFlushingPostCbs) {
+      // it's possible for a postFlushCb to queue another job/cb combo,
+      // e.g. triggering a state update inside the updated hook.
+      if (postFlushCbsForNextTick.indexOf(postFlushCb) === -1) {
+        postFlushCbsForNextTick.push(postFlushCb)
+      }
+    } else if (postFlushCbs.indexOf(postFlushCb) === -1) {
+      postFlushCbs.push(postFlushCb)
+    }
   }
 }
 
+const seenJobs = new Map()
+const RECURSION_LIMIT = 100
+
 function flushJobs() {
+  seenJobs.clear()
+  isFlushing = true
   let job
   while ((job = queue.shift())) {
+    if (__DEV__) {
+      if (!seenJobs.has(job)) {
+        seenJobs.set(job, 1)
+      } else {
+        const count = seenJobs.get(job)
+        if (count > RECURSION_LIMIT) {
+          throw new Error('Maximum recursive updates exceeded')
+        } else {
+          seenJobs.set(job, count + 1)
+        }
+      }
+    }
     job()
   }
-  while ((job = postFlushCbs.shift())) {
-    job()
+  isFlushingPostCbs = true
+  if (postFlushCbsForNextTick.length > 0) {
+    const postFlushCbsFromPrevTick = postFlushCbsForNextTick.slice()
+    postFlushCbsForNextTick.length = 0
+    for (let i = 0; i < postFlushCbsFromPrevTick.length; i++) {
+      postFlushCbsFromPrevTick[i]()
+    }
+  }
+  for (let i = 0; i < postFlushCbs.length; i++) {
+    postFlushCbs[i]()
   }
-  hasPendingFlush = false
+  postFlushCbs.length = 0
+  isFlushingPostCbs = false
+  isFlushing = false
 }