Skip to content

Commit 92fd14a

Browse files
committed
Necessary changes to observer for full Tracker compatibility.
1 parent c9fa2e6 commit 92fd14a

File tree

3 files changed

+160
-62
lines changed

3 files changed

+160
-62
lines changed

src/core/observer/scheduler.js

Lines changed: 143 additions & 53 deletions
Original file line numberDiff line numberDiff line change
@@ -14,13 +14,22 @@ let has: { [key: number]: ?true } = {}
1414
let circular: { [key: number]: number } = {}
1515
let waiting = false
1616
let flushing = false
17+
let insideRun = false
1718
let index = 0
19+
const afterFlushCallbacks: Array<Function> = []
1820

1921
/**
2022
* Reset the scheduler's state.
2123
*/
2224
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+
}
2433
has = {}
2534
if (process.env.NODE_ENV !== 'production') {
2635
circular = {}
@@ -29,63 +38,115 @@ function resetSchedulerState () {
2938
}
3039

3140
/**
32-
* Flush both queues and run the watchers.
41+
* Flush the queue and run the watchers.
3342
*/
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+
3554
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+
}
68114
}
69115
}
70116
}
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+
}
71129

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()
79138
}
80139
}
140+
}
81141

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)
86149
}
87-
88-
resetSchedulerState()
89150
}
90151

91152
/**
@@ -108,10 +169,39 @@ export function queueWatcher (watcher: Watcher) {
108169
}
109170
queue.splice(Math.max(i, index) + 1, 0, watcher)
110171
}
111-
// queue the flush
112-
if (!waiting) {
113-
waiting = true
114-
nextTick(flushSchedulerQueue)
115-
}
172+
requireFlush()
116173
}
117174
}
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+
}

src/core/observer/watcher.js

Lines changed: 14 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@
22

33
import config from '../config'
44
import Dep, { pushTarget, popTarget } from './dep'
5-
import { queueWatcher } from './scheduler'
5+
import { queueWatcher, wrapWatcherGetter } from './scheduler'
66
import {
77
warn,
88
remove,
@@ -79,6 +79,7 @@ export default class Watcher {
7979
)
8080
}
8181
}
82+
this.getter = wrapWatcherGetter(this.getter);
8283
this.value = this.lazy
8384
? undefined
8485
: this.get()
@@ -89,15 +90,19 @@ export default class Watcher {
8990
*/
9091
get () {
9192
pushTarget(this)
92-
const value = this.getter.call(this.vm, this.vm)
93-
// "touch" every property so they are all tracked as
94-
// dependencies for deep watching
95-
if (this.deep) {
96-
traverse(value)
93+
try {
94+
const value = this.getter.call(this.vm, this.vm)
95+
// "touch" every property so they are all tracked as
96+
// dependencies for deep watching
97+
if (this.deep) {
98+
traverse(value)
99+
}
100+
this.cleanupDeps()
101+
return value
102+
}
103+
finally {
104+
popTarget()
97105
}
98-
popTarget()
99-
this.cleanupDeps()
100-
return value
101106
}
102107

103108
/**

src/core/util/index.js

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,3 +5,6 @@ export * from './options'
55
export * from './debug'
66
export * from './props'
77
export { defineReactive } from '../observer/index'
8+
export { default as Dep, pushTarget, popTarget } from '../observer/dep'
9+
export { afterFlush, forceFlush } from '../observer/scheduler'
10+
export { default as Watcher } from '../observer/watcher'

0 commit comments

Comments
 (0)