@@ -14,13 +14,22 @@ let has: { [key: number]: ?true } = {}
14
14
let circular: { [ key : number ] : number } = { }
15
15
let waiting = false
16
16
let flushing = false
17
+ let insideRun = false
17
18
let index = 0
19
+ const afterFlushCallbacks: Array< Function > = []
18
20
19
21
/**
20
22
* Reset the scheduler's state.
21
23
*/
22
24
function resetSchedulerState () {
23
- queue . length = 0
25
+ // if we got to the end of the queue, we can just empty the queue
26
+ if ( index === queue . length ) {
27
+ queue . length = 0
28
+ }
29
+ // else, we only remove watchers we ran
30
+ else {
31
+ queue . splice ( 0 , index )
32
+ }
24
33
has = { }
25
34
if (process.env.NODE_ENV !== 'production') {
26
35
circular = { }
@@ -29,63 +38,115 @@ function resetSchedulerState () {
29
38
}
30
39
31
40
/**
32
- * Flush both queues and run the watchers.
41
+ * Flush the queue and run the watchers.
33
42
*/
34
- function flushSchedulerQueue ( ) {
43
+ function flushSchedulerQueue ( maxUpdateCount ?: number ) {
44
+ if ( flushing ) {
45
+ throw new Error ( "Cannot flush while already flushing." )
46
+ }
47
+
48
+ if (insideRun) {
49
+ throw new Error ( "Cannot flush while running a watcher." )
50
+ }
51
+
52
+ maxUpdateCount = maxUpdateCount || config._maxUpdateCount
53
+
35
54
flushing = true
36
- let watcher , id , vm
37
-
38
- // Sort queue before flush.
39
- // This ensures that:
40
- // 1. Components are updated from parent to child. (because parent is always
41
- // created before the child)
42
- // 2. A component's user watchers are run before its render watcher (because
43
- // user watchers are created before the render watcher)
44
- // 3. If a component is destroyed during a parent component's watcher run,
45
- // its watchers can be skipped.
46
- queue . sort ( ( a , b ) => a . id - b . id )
47
-
48
- // do not cache length because more watchers might be pushed
49
- // as we run existing watchers
50
- for ( index = 0 ; index < queue . length ; index ++ ) {
51
- watcher = queue [ index ]
52
- id = watcher . id
53
- has [ id ] = null
54
- watcher . run ( )
55
- // in dev build, check and stop circular updates.
56
- if ( process . env . NODE_ENV !== 'production' && has [ id ] != null ) {
57
- circular [ id ] = ( circular [ id ] || 0 ) + 1
58
- if ( circular [ id ] > config . _maxUpdateCount ) {
59
- warn (
60
- 'You may have an infinite update loop ' + (
61
- watcher . user
62
- ? `in watcher with expression "${ watcher . expression } "`
63
- : `in a component render function.`
64
- ) ,
65
- watcher . vm
66
- )
67
- break
55
+ let watcher, id, vm, hookIndex
56
+
57
+ // a watcher's run can throw
58
+ try {
59
+ // Sort queue before flush.
60
+ // This ensures that:
61
+ // 1. Components are updated from parent to child. (because parent is always
62
+ // created before the child)
63
+ // 2. A component's user watchers are run before its render watcher (because
64
+ // user watchers are created before the render watcher)
65
+ // 3. If a component is destroyed during a parent component's watcher run,
66
+ // its watchers can be skipped.
67
+ queue . sort ( ( a , b ) => a . id - b . id )
68
+
69
+ index = 0
70
+ while ( queue . length - index || afterFlushCallbacks . length ) {
71
+ // do not cache length because more watchers might be pushed
72
+ // as we run existing watchers
73
+ for ( ; index < queue . length ; index ++ ) {
74
+ watcher = queue [ index ]
75
+ id = watcher . id
76
+ has [ id ] = null
77
+ watcher . run ( )
78
+ // in dev build, check and stop circular updates.
79
+ if ( process . env . NODE_ENV !== 'production' && has [ id ] != null ) {
80
+ circular [ id ] = ( circular [ id ] || 0 ) + 1
81
+ if ( circular [ id ] > maxUpdateCount ) {
82
+ warn (
83
+ 'You may have an infinite update loop ' + (
84
+ watcher . user
85
+ ? `in watcher with expression "${ watcher . expression } "`
86
+ : `in a component render function.`
87
+ ) ,
88
+ watcher . vm
89
+ )
90
+ // to remove the whole current queue
91
+ index = queue . length
92
+ break
93
+ }
94
+ }
95
+ }
96
+
97
+ if ( afterFlushCallbacks . length ) {
98
+ // call one afterFlush callback, which may queue more watchers
99
+ // TODO: Optimize to not modify array at every run.
100
+ let func = afterFlushCallbacks . shift ( )
101
+ try {
102
+ func ( )
103
+ } catch ( e ) {
104
+ /* istanbul ignore else */
105
+ if ( config . errorHandler ) {
106
+ config . errorHandler . call ( null , e )
107
+ } else {
108
+ process . env . NODE_ENV !== 'production' && warn (
109
+ `Error in an after flush callback.`
110
+ )
111
+ throw e
112
+ }
113
+ }
68
114
}
69
115
}
70
116
}
117
+ finally {
118
+ // a hook can throw as well
119
+ try {
120
+ // call updated hooks
121
+ hookIndex = index
122
+ while ( hookIndex -- ) {
123
+ watcher = queue [ hookIndex ]
124
+ vm = watcher . vm
125
+ if ( vm . _watcher === watcher && vm . _isMounted ) {
126
+ callHook ( vm , 'updated' )
127
+ }
128
+ }
71
129
72
- // call updated hooks
73
- index = queue . length
74
- while ( index -- ) {
75
- watcher = queue [ index ]
76
- vm = watcher . vm
77
- if ( vm . _watcher === watcher && vm . _isMounted ) {
78
- callHook ( vm , 'updated' )
130
+ // devtool hook
131
+ /* istanbul ignore if */
132
+ if ( devtools && config . devtools ) {
133
+ devtools . emit ( 'flush' )
134
+ }
135
+ }
136
+ finally {
137
+ resetSchedulerState ( )
79
138
}
80
139
}
140
+ }
81
141
82
- // devtool hook
83
- /* istanbul ignore if */
84
- if ( devtools && config . devtools ) {
85
- devtools . emit ( 'flush' )
142
+ /**
143
+ * Queue the flush.
144
+ */
145
+ function requireFlush ( ) {
146
+ if ( ! waiting ) {
147
+ waiting = true
148
+ nextTick ( flushSchedulerQueue )
86
149
}
87
-
88
- resetSchedulerState ( )
89
150
}
90
151
91
152
/**
@@ -108,10 +169,39 @@ export function queueWatcher (watcher: Watcher) {
108
169
}
109
170
queue.splice(Math.max(i, index) + 1, 0, watcher)
110
171
}
111
- // queue the flush
112
- if ( ! waiting ) {
113
- waiting = true
114
- nextTick ( flushSchedulerQueue )
115
- }
172
+ requireFlush ( )
116
173
}
117
174
}
175
+
176
+ /**
177
+ * Schedules a function to be called after the next flush, or later in the
178
+ * current flush if one is in progress, after all watchers have been rerun.
179
+ * The function will be run once and not on subsequent flushes unless
180
+ * `afterFlush` is called again.
181
+ */
182
+ export function afterFlush ( f : Function ) {
183
+ afterFlushCallbacks . push ( f )
184
+ requireFlush ( )
185
+ }
186
+
187
+ /**
188
+ * Forces a synchronous flush.
189
+ */
190
+ export function forceFlush (maxUpdateCount?: number) {
191
+ flushSchedulerQueue ( maxUpdateCount )
192
+ }
193
+
194
+ /**
195
+ * Used in watchers to wrap provided getters to set scheduler flags.
196
+ */
197
+ export function wrapWatcherGetter (f: Function): Function {
198
+ return function ( /* args */ ) {
199
+ const previousInsideRun = insideRun
200
+ insideRun = true
201
+ try {
202
+ return f . apply ( this , arguments )
203
+ } finally {
204
+ insideRun = previousInsideRun
205
+ }
206
+ }
207
+ }
0 commit comments