import { ErrorCodes , callWithErrorHandling } from './errorHandling' ; import { isArray } from '@vue/shared' ; import { ComponentInternalInstance , getComponentName } from './component' ; import { warn } from './warning' ; export interface SchedulerJob extends Function { id ? : number ; active ? : boolean ; computed ? : boolean ; /**
     * Indicates whether the effect is allowed to recursively trigger itself
     * when managed by the scheduler.
     *
     * By default, a job cannot trigger itself because some built-in method calls,
     * e.g. Array.prototype.push actually performs reads as well (#1740) which
     * can lead to confusing infinite loops.
     * The allowed cases are component update functions and watch callbacks.
     * Component update functions may update child component props, which in turn
     * trigger flush: "pre" watch callbacks that mutates state that the parent
     * relies on (#1801). Watch callbacks doesn't track its dependencies so if it
     * triggers itself again, it's likely intentional and it is the user's
     * responsibility to perform recursive state mutation that eventually
     * stabilizes (#1727).
     */ allowRecurse ? : boolean ; /**
     * Attached by renderer.ts when setting up a component's render effect
     * Used to obtain component information when reporting max recursive updates.
     * dev only.
     */ ownerInstance ? : ComponentInternalInstance ; } export type SchedulerJobs = SchedulerJob | SchedulerJob [ ] ; let isFlushing = false ; let isFlushPending = false ; const queue = [ ] ; let flushIndex = 0 ; const pendingPreFlushCbs = [ ] ; let activePreFlushCbs = null ; let preFlushIndex = 0 ; const pendingPostFlushCbs = [ ] ; let activePostFlushCbs = null ; let postFlushIndex = 0 ; const resolvedPromise = Promise . resolve ( ) ; let currentFlushPromise = null ; let currentPreFlushParentJob = null ; const RECURSION_LIMIT = 100 ; type CountMap = Map < SchedulerJob , number > ; export function nextTick < T = void > ( this , fn ? ) { const p = currentFlushPromise || resolvedPromise ; return fn ? p . then ( this ? fn . bind ( this ) : fn ) : p ; } function findInsertionIndex ( id ) { let start = flushIndex + 1 ; let end = queue . length ; while ( start < end ) { const middle = ( start + end ) >>> 1 ; const middleJobId = getId ( queue [ middle ] ) ; middleJobId < id ? ( start = middle + 1 ) : ( end = middle ) ; } return start ; } export function queueJob ( job ) { if ( ( ! queue . length || ! queue . includes ( job , isFlushing && job . allowRecurse ? flushIndex + 1 : flushIndex ) ) && job !== currentPreFlushParentJob ) { if ( job . id == null ) { queue . push ( job ) ; } else { queue . splice ( findInsertionIndex ( job . id ) , 0 , job ) ; } queueFlush ( ) ; } } function queueFlush ( ) { if ( ! isFlushing && ! isFlushPending ) { isFlushPending = true ; currentFlushPromise = resolvedPromise . then ( flushJobs ) ; } } export function invalidateJob ( job ) { const i = queue . indexOf ( job ) ; if ( i > flushIndex ) { queue . splice ( i , 1 ) ; } } function queueCb ( cb , activeQueue , pendingQueue , index ) { if ( ! isArray ( cb ) ) { if ( ! activeQueue || ! activeQueue . includes ( cb , cb . allowRecurse ? index + 1 : index ) ) { pendingQueue . push ( cb ) ; } } else { pendingQueue . push ( ... cb ) ; } queueFlush ( ) ; } export function queuePreFlushCb ( cb ) { queueCb ( cb , activePreFlushCbs , pendingPreFlushCbs , preFlushIndex ) ; } export function queuePostFlushCb ( cb ) { queueCb ( cb , activePostFlushCbs , pendingPostFlushCbs , postFlushIndex ) ; } export function flushPreFlushCbs ( seen ? , parentJob = 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 ] ) ) { continue ; } activePreFlushCbs [ preFlushIndex ] ( ) ; } activePreFlushCbs = null ; preFlushIndex = 0 ; currentPreFlushParentJob = null ; flushPreFlushCbs ( seen , parentJob ) ; } } export function flushPostFlushCbs ( seen ? ) { if ( pendingPostFlushCbs . length ) { const deduped = [ ... new Set ( pendingPostFlushCbs ) ] ; pendingPostFlushCbs . length = 0 ; if ( activePostFlushCbs ) { activePostFlushCbs . push ( ... deduped ) ; return ; } activePostFlushCbs = deduped ; if ( __DEV__ ) { seen = seen || new Map ( ) ; } activePostFlushCbs . sort ( ( a , b ) => getId ( a ) - getId ( b ) ) ; for ( postFlushIndex = 0 ; postFlushIndex < activePostFlushCbs . length ; postFlushIndex ++ ) { if ( __DEV__ && checkRecursiveUpdates ( seen ! , activePostFlushCbs [ postFlushIndex ] ) ) { continue ; } activePostFlushCbs [ postFlushIndex ] ( ) ; } activePostFlushCbs = null ; postFlushIndex = 0 ; } } const getId = ( job ) : number => job . id == null ? Infinity : job . id ; function flushJobs ( seen ? ) { isFlushPending = false ; isFlushing = true ; if ( __DEV__ ) { seen = seen || new Map ( ) ; } flushPreFlushCbs ( seen ) ; queue . sort ( ( a , b ) => getId ( a ) - getId ( b ) ) ; try { for ( flushIndex = 0 ; flushIndex < queue . length ; flushIndex ++ ) { const job = queue [ flushIndex ] ; if ( job && job . active !== false ) { if ( __DEV__ && checkRecursiveUpdates ( seen ! , job ) ) { continue ; } callWithErrorHandling ( job , null , ErrorCodes . SCHEDULER ) ; } } } finally { flushIndex = 0 ; queue . length = 0 ; flushPostFlushCbs ( seen ) ; isFlushing = false ; currentFlushPromise = null ; if ( queue . length || pendingPreFlushCbs . length || pendingPostFlushCbs . length ) { flushJobs ( seen ) ; } } } function checkRecursiveUpdates ( seen , fn ) { if ( ! seen . has ( fn ) ) { seen . set ( fn , 1 ) ; } else { const count = seen . get ( fn ) ! ; if ( count > RECURSION_LIMIT ) { const instance = fn . ownerInstance ; const componentName = instance && getComponentName ( instance . type ) ; warn ( `Maximum recursive updates exceeded${ componentName ? ` in component <${ componentName }>` : `` }. ` + `This means you have a reactive effect that is mutating its own ` + `dependencies and thus recursively triggering itself. Possible sources ` + `include component template, render function, updated hook or ` + `watcher source function.` ) ; return true ; } else { seen . set ( fn , count + 1 ) ; } } }