import { ErrorCodes, callWithErrorHandling } from './errorHandling' import { isArray } from '@vue/shared' const queue: Function[] = [] const postFlushCbs: Function[] = [] const p = Promise.resolve() let isFlushing = false let isFlushPending = false const RECURSION_LIMIT = 100 type CountMap = Map export function nextTick(fn?: () => void): Promise { return fn ? p.then(fn) : p } export function queueJob(job: () => void) { if (!queue.includes(job)) { queue.push(job) queueFlush() } } export function queuePostFlushCb(cb: Function | Function[]) { if (!isArray(cb)) { postFlushCbs.push(cb) } else { postFlushCbs.push(...cb) } queueFlush() } function queueFlush() { if (!isFlushing && !isFlushPending) { isFlushPending = true nextTick(flushJobs) } } const dedupe = (cbs: Function[]): Function[] => [...new Set(cbs)] export function flushPostFlushCbs(seen?: CountMap) { if (postFlushCbs.length) { const cbs = dedupe(postFlushCbs) postFlushCbs.length = 0 if (__DEV__) { seen = seen || new Map() } for (let i = 0; i < cbs.length; i++) { if (__DEV__) { checkRecursiveUpdates(seen!, cbs[i]) } cbs[i]() } } } function flushJobs(seen?: CountMap) { isFlushPending = false isFlushing = true let job if (__DEV__) { seen = seen || new Map() } while ((job = queue.shift())) { if (__DEV__) { checkRecursiveUpdates(seen!, job) } callWithErrorHandling(job, null, ErrorCodes.SCHEDULER) } flushPostFlushCbs(seen) isFlushing = false // some postFlushCb queued jobs! // keep flushing until it drains. if (queue.length || postFlushCbs.length) { flushJobs(seen) } } function checkRecursiveUpdates(seen: CountMap, fn: Function) { if (!seen.has(fn)) { seen.set(fn, 1) } else { const count = seen.get(fn)! if (count > RECURSION_LIMIT) { throw new Error( 'Maximum recursive updates exceeded. ' + "You may have code that is mutating state in your component's " + 'render function or updated hook or watcher source function.' ) } else { seen.set(fn, count + 1) } } }