Skip to content

Commit 579c678

Browse files
author
Taras Batenkov
committed
feat: add error handling for Promises / async (vuejs#7653)
1 parent 52719cc commit 579c678

File tree

4 files changed

+93
-4
lines changed

4 files changed

+93
-4
lines changed

src/core/instance/events.js

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,8 @@ import {
55
toArray,
66
hyphenate,
77
handleError,
8-
formatComponentName
8+
formatComponentName,
9+
handlePromiseError
910
} from '../util/index'
1011
import { updateListeners } from '../vdom/helpers/index'
1112

@@ -131,7 +132,8 @@ export function eventsMixin (Vue: Class<Component>) {
131132
const args = toArray(arguments, 1)
132133
for (let i = 0, l = cbs.length; i < l; i++) {
133134
try {
134-
cbs[i].apply(vm, args)
135+
const cbResult = cbs[i].apply(vm, args)
136+
handlePromiseError(cbResult, vm, `${hook} hook`)
135137
} catch (e) {
136138
handleError(e, vm, `event handler for "${event}"`)
137139
}

src/core/instance/lifecycle.js

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,8 @@ import {
1515
remove,
1616
handleError,
1717
emptyObject,
18-
validateProp
18+
validateProp,
19+
handlePromiseError
1920
} from '../util/index'
2021

2122
export let activeInstance: any = null
@@ -319,7 +320,8 @@ export function callHook (vm: Component, hook: string) {
319320
if (handlers) {
320321
for (let i = 0, j = handlers.length; i < j; i++) {
321322
try {
322-
handlers[i].call(vm)
323+
const fnResult = handlers[i].call(vm)
324+
handlePromiseError(fnResult, vm, `${hook} hook`)
323325
} catch (e) {
324326
handleError(e, vm, `${hook} hook`)
325327
}

src/core/util/error.js

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,13 @@ export function handleError (err: Error, vm: any, info: string) {
2424
globalHandleError(err, vm, info)
2525
}
2626

27+
export function handlePromiseError(value: any, vm: any, info: string) {
28+
// if value is promise, handle it
29+
if (value && typeof value.catch === 'function') {
30+
value.catch(e => handleError(e, vm, `${hook} hook`))
31+
}
32+
}
33+
2734
function globalHandleError (err, vm, info) {
2835
if (config.errorHandler) {
2936
try {

test/unit/features/error-handling.spec.js

Lines changed: 78 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,23 @@ describe('Error handling', () => {
2222
})
2323
})
2424

25+
// hooks that can return promise
26+
;[
27+
['beforeCreate', 'beforeCreate hook'],
28+
['created', 'created hook'],
29+
['beforeMount', 'beforeMount hook'],
30+
['mounted', 'mounted hook'],
31+
['event', 'event handler for "e"']
32+
].forEach(([type, description]) => {
33+
it(`should recover from promise errors in ${type}`, done => {
34+
createTestInstance(components[`${type}Async`])
35+
waitForUpdate(() => {
36+
expect(`Error in ${description}`).toHaveBeenWarned()
37+
expect(`Error: ${type}`).toHaveBeenWarned()
38+
}).then(done)
39+
})
40+
})
41+
2542
// error in mounted hook should affect neither child nor parent
2643
it('should recover from errors in mounted hook', done => {
2744
const vm = createTestInstance(components.mounted)
@@ -45,6 +62,20 @@ describe('Error handling', () => {
4562
})
4663
})
4764

65+
// hooks that can return promise
66+
;[
67+
['beforeUpdate', 'beforeUpdate hook'],
68+
['updated', 'updated hook']
69+
].forEach(([type, description]) => {
70+
it(`should recover from promise errors in ${type} hook`, done => {
71+
const vm = createTestInstance(components[`${type}Async`])
72+
assertBothInstancesActive(vm).then(() => {
73+
expect(`Error in ${description}`).toHaveBeenWarned()
74+
expect(`Error: ${type}`).toHaveBeenWarned()
75+
}).then(done)
76+
})
77+
})
78+
4879
;[
4980
['beforeDestroy', 'beforeDestroy hook'],
5081
['destroyed', 'destroyed hook'],
@@ -62,6 +93,21 @@ describe('Error handling', () => {
6293
})
6394
})
6495

96+
;[
97+
['beforeDestroy', 'beforeDestroy hook'],
98+
['destroyed', 'destroyed hook']
99+
].forEach(([type, description]) => {
100+
it(`should recover from promise errors in ${type} hook`, done => {
101+
const vm = createTestInstance(components[`${type}Async`])
102+
vm.ok = false
103+
setTimeout(() => {
104+
expect(`Error in ${description}`).toHaveBeenWarned()
105+
expect(`Error: ${type}`).toHaveBeenWarned()
106+
assertRootInstanceActive(vm).then(done)
107+
})
108+
})
109+
})
110+
65111
it('should recover from errors in user watcher getter', done => {
66112
const vm = createTestInstance(components.userWatcherGetter)
67113
vm.n++
@@ -178,6 +224,16 @@ function createErrorTestComponents () {
178224
throw new Error(before)
179225
}
180226

227+
const beforeCompAsync = components[`${before}Async`] = {
228+
props: ['n'],
229+
render (h) {
230+
return h('div', this.n)
231+
}
232+
}
233+
beforeCompAsync[before] = function () {
234+
return new Promise((resolve, reject) => reject(new Error(before)))
235+
}
236+
181237
// after
182238
const after = hook.replace(/e?$/, 'ed')
183239
const afterComp = components[after] = {
@@ -189,6 +245,16 @@ function createErrorTestComponents () {
189245
afterComp[after] = function () {
190246
throw new Error(after)
191247
}
248+
249+
const afterCompAsync = components[`${after}Async`] = {
250+
props: ['n'],
251+
render (h) {
252+
return h('div', this.n)
253+
}
254+
}
255+
afterCompAsync[after] = function () {
256+
return new Promise((resolve, reject) => reject(new Error(after)))
257+
}
192258
})
193259

194260
// directive hooks errors
@@ -247,6 +313,18 @@ function createErrorTestComponents () {
247313
}
248314
}
249315

316+
components.eventAsync = {
317+
beforeCreate () {
318+
this.$on('e', () => new Promise((resolve, reject) => reject(new Error('event'))))
319+
},
320+
mounted () {
321+
this.$emit('e')
322+
},
323+
render (h) {
324+
return h('div')
325+
}
326+
}
327+
250328
return components
251329
}
252330

0 commit comments

Comments
 (0)