Skip to content

Commit 2732fec

Browse files
committed
tweak error handling
1 parent 93fb4df commit 2732fec

File tree

6 files changed

+68
-65
lines changed

6 files changed

+68
-65
lines changed

src/core/instance/render.js

Lines changed: 24 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@ import config from '../config'
44
import VNode, { emptyVNode } from '../vdom/vnode'
55
import { normalizeChildren } from '../vdom/helpers'
66
import {
7-
warn, bind, isObject, toObject,
7+
warn, formatComponentName, bind, isObject, toObject,
88
nextTick, resolveAsset, _toString, toNumber
99
} from '../util/index'
1010

@@ -48,19 +48,38 @@ export function renderMixin (Vue: Class<Component>) {
4848
_parentVnode
4949
} = vm.$options
5050

51-
if (staticRenderFns && !this._staticTrees) {
52-
this._staticTrees = []
51+
if (staticRenderFns && !vm._staticTrees) {
52+
vm._staticTrees = []
5353
}
5454
// set parent vnode. this allows render functions to have access
5555
// to the data on the placeholder node.
56-
this.$vnode = _parentVnode
56+
vm.$vnode = _parentVnode
5757
// resolve slots. becaues slots are rendered in parent scope,
5858
// we set the activeInstance to parent.
5959
if (_renderChildren) {
6060
resolveSlots(vm, _renderChildren)
6161
}
6262
// render self
63-
let vnode = render.call(vm._renderProxy, vm.$createElement)
63+
let vnode
64+
try {
65+
vnode = render.call(vm._renderProxy, vm.$createElement)
66+
} catch (e) {
67+
if (process.env.NODE_ENV !== 'production') {
68+
warn(`Error when rendering ${formatComponentName(vm)}:`)
69+
}
70+
/* istanbul ignore else */
71+
if (config.errorHandler) {
72+
config.errorHandler.call(null, e, vm)
73+
} else {
74+
if (config._isServer) {
75+
throw e
76+
} else {
77+
setTimeout(() => { throw e }, 0)
78+
}
79+
}
80+
// return previous vnode to prevent render error causing blank component
81+
vnode = vm._vnode
82+
}
6483
// return empty vnode in case the render function errored out
6584
if (!(vnode instanceof VNode)) {
6685
if (process.env.NODE_ENV !== 'production' && Array.isArray(vnode)) {

src/core/observer/scheduler.js

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -82,9 +82,9 @@ function runSchedulerQueue (queue: Array<Watcher>) {
8282
if (circular[id] > config._maxUpdateCount) {
8383
warn(
8484
'You may have an infinite update loop ' + (
85-
watcher === watcher.vm && watcher.vm._watcher
86-
? `in a component render function.`
87-
: `in watcher with expression "${watcher.expression}"`
85+
watcher.user
86+
? `in watcher with expression "${watcher.expression}"`
87+
: `in a component render function.`
8888
),
8989
watcher.vm
9090
)

src/core/observer/watcher.js

Lines changed: 1 addition & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -83,33 +83,7 @@ export default class Watcher {
8383
*/
8484
get () {
8585
pushTarget(this)
86-
let value: any
87-
try {
88-
value = this.getter.call(this.vm, this.vm)
89-
} catch (e) {
90-
if (process.env.NODE_ENV !== 'production') {
91-
if (this.user) {
92-
warn(
93-
'Error when evaluating watcher with getter: ' + this.expression,
94-
this.vm
95-
)
96-
} else {
97-
warn(
98-
'Error during component render',
99-
this.vm
100-
)
101-
}
102-
}
103-
/* istanbul ignore else */
104-
if (config.errorHandler) {
105-
config.errorHandler.call(null, e, this.vm)
106-
} else {
107-
console.error(e)
108-
}
109-
// return old value when evaluation fails so the current UI is preserved
110-
// if the error was somehow handled by user
111-
value = this.value
112-
}
86+
const value = this.getter.call(this.vm, this.vm)
11387
// "touch" every property so they are all tracked as
11488
// dependencies for deep watching
11589
if (this.deep) {

src/core/util/debug.js

Lines changed: 13 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,4 @@
11
import config from '../config'
2-
import { hyphenate } from 'shared/util'
32

43
let warn
54
let formatComponentName
@@ -9,21 +8,28 @@ if (process.env.NODE_ENV !== 'production') {
98

109
warn = (msg, vm) => {
1110
if (hasConsole && (!config.silent)) {
12-
console.error('[Vue warn]: ' + msg + (vm ? formatComponentName(vm) : ''))
11+
console.error(`[Vue warn]: ${msg} ` + (
12+
vm ? formatLocation(formatComponentName(vm)) : ''
13+
))
1314
}
1415
}
1516

1617
formatComponentName = vm => {
1718
if (vm.$root === vm) {
18-
return ' (found in root instance)'
19+
return 'root instance'
1920
}
2021
const name = vm._isVue
2122
? vm.$options.name || vm.$options._componentTag
2223
: vm.name
23-
return name
24-
? ' (found in component: <' + hyphenate(name) + '>)'
25-
: ' (found in anonymous component. Use the "name" option for better debugging messages)'
24+
return name ? `component <${name}>` : `anonymous component`
25+
}
26+
27+
const formatLocation = str => {
28+
if (str === 'anonymous component') {
29+
str += ` - use the "name" option for better debugging messages.)`
30+
}
31+
return `(found in ${str})`
2632
}
2733
}
2834

29-
export { warn }
35+
export { warn, formatComponentName }

test/unit/features/component/component.spec.js

Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -276,4 +276,31 @@ describe('Component', () => {
276276
expect(vm.$el.className).toBe('test red')
277277
}).then(done)
278278
})
279+
280+
it('catch component render error and preserve previous vnode', done => {
281+
const spy = jasmine.createSpy()
282+
Vue.config.errorHandler = spy
283+
const vm = new Vue({
284+
data: {
285+
a: {
286+
b: 123
287+
}
288+
},
289+
render (h) {
290+
return h('div', [this.a.b])
291+
}
292+
}).$mount()
293+
expect(vm.$el.textContent).toBe('123')
294+
expect(spy).not.toHaveBeenCalled()
295+
vm.a = null
296+
waitForUpdate(() => {
297+
expect('Error when rendering root instance').toHaveBeenWarned()
298+
expect(spy).toHaveBeenCalled()
299+
expect(vm.$el.textContent).toBe('123') // should preserve rendered DOM
300+
vm.a = { b: 234 }
301+
}).then(() => {
302+
expect(vm.$el.textContent).toBe('234') // should be able to recover
303+
Vue.config.errorHandler = null
304+
}).then(done)
305+
})
279306
})

test/unit/modules/observer/watcher.spec.js

Lines changed: 0 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -178,27 +178,4 @@ describe('Watcher', () => {
178178
new Watcher(vm, 'd.e + c', spy)
179179
expect('Failed watching path:').toHaveBeenWarned()
180180
})
181-
182-
it('catch getter error', () => {
183-
Vue.config.errorHandler = spy
184-
const err = new Error()
185-
const vm = new Vue({
186-
render () { throw err }
187-
}).$mount()
188-
expect('Error during component render').toHaveBeenWarned()
189-
expect(spy).toHaveBeenCalledWith(err, vm)
190-
Vue.config.errorHandler = null
191-
})
192-
193-
it('catch user watcher error', () => {
194-
Vue.config.errorHandler = spy
195-
new Watcher(vm, function () {
196-
return this.a.b.c
197-
}, () => {}, { user: true })
198-
expect('Error when evaluating watcher').toHaveBeenWarned()
199-
expect(spy).toHaveBeenCalled()
200-
expect(spy.calls.argsFor(0)[0] instanceof TypeError).toBe(true)
201-
expect(spy.calls.argsFor(0)[1]).toBe(vm)
202-
Vue.config.errorHandler = null
203-
})
204181
})

0 commit comments

Comments
 (0)