diff --git a/src/core/util/error.js b/src/core/util/error.js index 3e69ac5bf74..5b5d60c80fe 100644 --- a/src/core/util/error.js +++ b/src/core/util/error.js @@ -24,9 +24,9 @@ export function handleError (err: Error, vm: any, info: string) { globalHandleError(err, vm, info) } -export function handlePromiseError(value: any, vm: any, info: string) { - // if value is promise, handle it - if (value && typeof value.catch === 'function') { +export function handlePromiseError (value: any, vm: any, info: string) { + // if value is promise, handle it (a promise must have a then function) + if (value && typeof value.then === 'function' && typeof value.catch === 'function') { value.catch(e => handleError(e, vm, info)) } } diff --git a/src/core/vdom/helpers/update-listeners.js b/src/core/vdom/helpers/update-listeners.js index 11c5a2cd2f8..001ba819779 100644 --- a/src/core/vdom/helpers/update-listeners.js +++ b/src/core/vdom/helpers/update-listeners.js @@ -1,6 +1,6 @@ /* @flow */ -import { warn } from 'core/util/index' +import { warn, handleError, handlePromiseError } from 'core/util/index' import { cached, isUndef, isPlainObject } from 'shared/util' const normalizeEvent = cached((name: string): { @@ -25,17 +25,29 @@ const normalizeEvent = cached((name: string): { } }) -export function createFnInvoker (fns: Function | Array): Function { +export function createFnInvoker (fns: Function | Array, vm: ?Component): Function { function invoker () { const fns = invoker.fns if (Array.isArray(fns)) { const cloned = fns.slice() for (let i = 0; i < cloned.length; i++) { - cloned[i].apply(null, arguments) + try { + const result = cloned[i].apply(null, arguments) + handlePromiseError(result, vm, 'v-on async') + } catch (e) { + handleError(e, vm, 'v-on') + } } } else { // return handler return value for single handlers - return fns.apply(null, arguments) + let result + try { + result = fns.apply(null, arguments) + handlePromiseError(result, vm, 'v-on async') + } catch (e) { + handleError(e, vm, 'v-on') + } + return result } } invoker.fns = fns @@ -66,7 +78,7 @@ export function updateListeners ( ) } else if (isUndef(old)) { if (isUndef(cur.fns)) { - cur = on[name] = createFnInvoker(cur) + cur = on[name] = createFnInvoker(cur, vm) } add(event.name, cur, event.once, event.capture, event.passive, event.params) } else if (cur !== old) { diff --git a/test/unit/features/error-handling.spec.js b/test/unit/features/error-handling.spec.js index bf829e56e7b..00e86c5eb4b 100644 --- a/test/unit/features/error-handling.spec.js +++ b/test/unit/features/error-handling.spec.js @@ -188,6 +188,35 @@ describe('Error handling', () => { expect(vm.$el.textContent).toContain('error in render') Vue.config.errorHandler = null }) + + it('should capture and recover from v-on errors', () => { + const spy = Vue.config.errorHandler = jasmine.createSpy('errorHandler') + const err1 = new Error('clickbork') + const vm = new Vue({ + template: '
', + methods: { bork: function () { throw err1 } } + }).$mount() + triggerEvent(vm.$el, 'click') + expect(spy.calls.count()).toBe(1) + expect(spy).toHaveBeenCalledWith(err1, vm, 'v-on') + Vue.config.errorHandler = null + }) + + it('should capture and recover from v-on async errors', (done) => { + const spy = Vue.config.errorHandler = jasmine.createSpy('errorHandler') + const err1 = new Error('clickbork') + const vm = new Vue({ + template: '
', + methods: { bork: function () { return new Promise(function (_resolve, reject) { reject(err1) }) } } + }).$mount() + triggerEvent(vm.$el, 'click') + Vue.nextTick(() => { + expect(spy.calls.count()).toBe(1) + expect(spy).toHaveBeenCalledWith(err1, vm, 'v-on async') + Vue.config.errorHandler = null + done() + }) + }) }) function createErrorTestComponents () {