nextTick,
queuePostFlushCb,
invalidateJob,
- queuePreFlushCb,
- flushPreFlushCbs,
- flushPostFlushCbs
+ flushPostFlushCbs,
+ flushPreFlushCbs
} from '../src/scheduler'
describe('scheduler', () => {
})
})
- describe('queuePreFlushCb', () => {
- it('basic usage', async () => {
- const calls: string[] = []
- const cb1 = () => {
- calls.push('cb1')
- }
- const cb2 = () => {
- calls.push('cb2')
- }
-
- queuePreFlushCb(cb1)
- queuePreFlushCb(cb2)
-
- expect(calls).toEqual([])
- await nextTick()
- expect(calls).toEqual(['cb1', 'cb2'])
- })
-
- it('should dedupe queued preFlushCb', async () => {
- const calls: string[] = []
- const cb1 = () => {
- calls.push('cb1')
- }
- const cb2 = () => {
- calls.push('cb2')
- }
- const cb3 = () => {
- calls.push('cb3')
- }
-
- queuePreFlushCb(cb1)
- queuePreFlushCb(cb2)
- queuePreFlushCb(cb1)
- queuePreFlushCb(cb2)
- queuePreFlushCb(cb3)
-
- expect(calls).toEqual([])
- await nextTick()
- expect(calls).toEqual(['cb1', 'cb2', 'cb3'])
- })
-
- it('chained queuePreFlushCb', async () => {
- const calls: string[] = []
- const cb1 = () => {
- calls.push('cb1')
- // cb2 will be executed after cb1 at the same tick
- queuePreFlushCb(cb2)
- }
- const cb2 = () => {
- calls.push('cb2')
- }
- queuePreFlushCb(cb1)
-
- await nextTick()
- expect(calls).toEqual(['cb1', 'cb2'])
- })
- })
-
- describe('queueJob w/ queuePreFlushCb', () => {
+ describe('pre flush jobs', () => {
it('queueJob inside preFlushCb', async () => {
const calls: string[] = []
const job1 = () => {
calls.push('cb1')
queueJob(job1)
}
+ cb1.pre = true
- queuePreFlushCb(cb1)
+ queueJob(cb1)
await nextTick()
expect(calls).toEqual(['cb1', 'job1'])
})
const job1 = () => {
calls.push('job1')
}
+ job1.id = 1
+
const cb1 = () => {
calls.push('cb1')
queueJob(job1)
// cb2 should execute before the job
- queuePreFlushCb(cb2)
+ queueJob(cb2)
}
+ cb1.pre = true
+
const cb2 = () => {
calls.push('cb2')
}
+ cb2.pre = true
+ cb2.id = 1
- queuePreFlushCb(cb1)
+ queueJob(cb1)
await nextTick()
expect(calls).toEqual(['cb1', 'cb2', 'job1'])
})
// when updating the props of a child component. This is handled
// directly inside `updateComponentPreRender` to avoid non atomic
// cb triggers (#1763)
- queuePreFlushCb(cb1)
- queuePreFlushCb(cb2)
- flushPreFlushCbs(undefined, job1)
+ queueJob(cb1)
+ queueJob(cb2)
+ flushPreFlushCbs()
calls.push('job1')
}
const cb1 = () => {
// a cb triggers its parent job, which should be skipped
queueJob(job1)
}
+ cb1.pre = true
const cb2 = () => {
calls.push('cb2')
}
+ cb2.pre = true
queueJob(job1)
await nextTick()
// #3806
it('queue preFlushCb inside postFlushCb', async () => {
- const cb = jest.fn()
+ const spy = jest.fn()
+ const cb = () => spy()
+ cb.pre = true
queuePostFlushCb(() => {
- queuePreFlushCb(cb)
+ queueJob(cb)
})
await nextTick()
- expect(cb).toHaveBeenCalled()
+ expect(spy).toHaveBeenCalled()
})
})
export interface SchedulerJob extends Function {
id?: number
+ pre?: boolean
active?: boolean
computed?: boolean
/**
const queue: SchedulerJob[] = []
let flushIndex = 0
-const pendingPreFlushCbs: SchedulerJob[] = []
-let activePreFlushCbs: SchedulerJob[] | null = null
-let preFlushIndex = 0
-
const pendingPostFlushCbs: SchedulerJob[] = []
let activePostFlushCbs: SchedulerJob[] | null = null
let postFlushIndex = 0
const resolvedPromise = /*#__PURE__*/ Promise.resolve() as Promise<any>
let currentFlushPromise: Promise<void> | null = null
-let currentPreFlushParentJob: SchedulerJob | null = null
-
const RECURSION_LIMIT = 100
type CountMap = Map<SchedulerJob, number>
// allow it recursively trigger itself - it is the user's responsibility to
// ensure it doesn't end up in an infinite loop.
if (
- (!queue.length ||
- !queue.includes(
- job,
- isFlushing && job.allowRecurse ? flushIndex + 1 : flushIndex
- )) &&
- job !== currentPreFlushParentJob
+ !queue.length ||
+ !queue.includes(
+ job,
+ isFlushing && job.allowRecurse ? flushIndex + 1 : flushIndex
+ )
) {
if (job.id == null) {
queue.push(job)
}
}
-function queueCb(
- cb: SchedulerJobs,
- activeQueue: SchedulerJob[] | null,
- pendingQueue: SchedulerJob[],
- index: number
-) {
+export function queuePostFlushCb(cb: SchedulerJobs) {
if (!isArray(cb)) {
if (
- !activeQueue ||
- !activeQueue.includes(cb, cb.allowRecurse ? index + 1 : index)
+ !activePostFlushCbs ||
+ !activePostFlushCbs.includes(
+ cb,
+ cb.allowRecurse ? postFlushIndex + 1 : postFlushIndex
+ )
) {
- pendingQueue.push(cb)
+ pendingPostFlushCbs.push(cb)
}
} else {
// if cb is an array, it is a component lifecycle hook which can only be
// triggered by a job, which is already deduped in the main queue, so
// we can skip duplicate check here to improve perf
- pendingQueue.push(...cb)
+ pendingPostFlushCbs.push(...cb)
}
queueFlush()
}
-export function queuePreFlushCb(cb: SchedulerJob) {
- queueCb(cb, activePreFlushCbs, pendingPreFlushCbs, preFlushIndex)
-}
-
-export function queuePostFlushCb(cb: SchedulerJobs) {
- queueCb(cb, activePostFlushCbs, pendingPostFlushCbs, postFlushIndex)
-}
-
-export function flushPreFlushCbs(
- seen?: CountMap,
- parentJob: SchedulerJob | null = null
-) {
- if (pendingPreFlushCbs.length) {
- currentPreFlushParentJob = parentJob
- activePreFlushCbs = [...new Set(pendingPreFlushCbs)]
- pendingPreFlushCbs.length = 0
- if (__DEV__) {
- seen = seen || new Map()
- }
- for (
- preFlushIndex = 0;
- preFlushIndex < activePreFlushCbs.length;
- preFlushIndex++
- ) {
- if (
- __DEV__ &&
- checkRecursiveUpdates(seen!, activePreFlushCbs[preFlushIndex])
- ) {
+export function flushPreFlushCbs(seen?: CountMap, i = flushIndex) {
+ if (__DEV__) {
+ seen = seen || new Map()
+ }
+ for (; i < queue.length; i++) {
+ const cb = queue[i]
+ if (cb && cb.pre) {
+ if (__DEV__ && checkRecursiveUpdates(seen!, cb)) {
continue
}
- activePreFlushCbs[preFlushIndex]()
+ queue.splice(i, 1)
+ i--
+ cb()
}
- activePreFlushCbs = null
- preFlushIndex = 0
- currentPreFlushParentJob = null
- // recursively flush until it drains
- flushPreFlushCbs(seen, parentJob)
}
}
export function flushPostFlushCbs(seen?: CountMap) {
- // flush any pre cbs queued during the flush (e.g. pre watchers)
- flushPreFlushCbs()
if (pendingPostFlushCbs.length) {
const deduped = [...new Set(pendingPostFlushCbs)]
pendingPostFlushCbs.length = 0
const getId = (job: SchedulerJob): number =>
job.id == null ? Infinity : job.id
+const comparator = (a: SchedulerJob, b: SchedulerJob): number => {
+ const diff = getId(a) - getId(b)
+ if (diff === 0) {
+ if (a.pre && !b.pre) return -1
+ if (b.pre && !a.pre) return 1
+ }
+ return diff
+}
+
function flushJobs(seen?: CountMap) {
isFlushPending = false
isFlushing = true
seen = seen || new Map()
}
- flushPreFlushCbs(seen)
-
// Sort queue before flush.
// This ensures that:
// 1. Components are updated from parent to child. (because parent is always
// priority number)
// 2. If a component is unmounted during a parent component's update,
// its update can be skipped.
- queue.sort((a, b) => getId(a) - getId(b))
+ queue.sort(comparator)
// conditional usage of checkRecursiveUpdate must be determined out of
// try ... catch block since Rollup by default de-optimizes treeshaking
currentFlushPromise = null
// some postFlushCb queued jobs!
// keep flushing until it drains.
- if (
- queue.length ||
- pendingPreFlushCbs.length ||
- pendingPostFlushCbs.length
- ) {
+ if (queue.length || pendingPostFlushCbs.length) {
flushJobs(seen)
}
}