Vue Error Handling
Vue instances have an errorCaptured
hook that Vue calls whenever an event handler or lifecycle hook throws an error. For example,
the below code will increment a counter because the child component test
throws an error every time the button is clicked.
Vue.component('test', {
template: '<button v-on:click="notAMethod()">Throw</button>'
});
const app = new Vue({
data: () => ({ count: 0 }),
errorCaptured: function(err) {
console.log('Caught error', err.message);
++this.count;
return false;
},
template: `
<div>
<span id="count">{{count}}</span>
<test></test>
</div>
`
});
errorCaptured
Only Catches Errors in Nested Components
A common gotcha is that Vue does not call errorCaptured
when the error occurs in the same component that the
errorCaptured
hook is registered on. For example, if you remove the 'test' component from the above example and
inline the button
in the top-level Vue instance, Vue will not call errorCaptured
.
const app = new Vue({
data: () => ({ count: 0 }),
// Vue won't call this hook, because the error occurs in this Vue
// instance, not a child component.
errorCaptured: function(err) {
console.log('Caught error', err.message);
++this.count;
return false;
},
template: `
<div>
<span id="count">{{count}}</span>
<button v-on:click="notAMethod()">Throw</button>
</div>
`
});
Async Errors
On the bright side, Vue does call errorCaptured()
when an async function throws an error. For example, if a child
component asynchronously throws an error, Vue will still bubble up the error to the parent.
Vue.component('test', {
methods: {
// Vue bubbles up async errors to the parent's `errorCaptured()`, so
// every time you click on the button, Vue will call the `errorCaptured()`
// hook with `err.message = 'Oops'`
test: async function test() {
await new Promise(resolve => setTimeout(resolve, 50));
throw new Error('Oops!');
}
},
template: '<button v-on:click="test()">Throw</button>'
});
const app = new Vue({
data: () => ({ count: 0 }),
errorCaptured: function(err) {
console.log('Caught error', err.message);
++this.count;
return false;
},
template: `
<div>
<span id="count">{{count}}</span>
<test></test>
</div>
`
});
Error Propagation
You might have noticed the return false
line in the previous examples. If your errorCaptured()
function does not return false
, Vue will bubble up the error to parent components' errorCaptured()
:
Vue.component('level2', {
template: '<button v-on:click="notAMethod()">Throw</button>'
});
Vue.component('level1', {
errorCaptured: function(err) {
console.log('Level 1 error', err.message);
},
template: '<level2></level2>'
});
const app = new Vue({
data: () => ({ count: 0 }),
errorCaptured: function(err) {
// Since the level1 component's `errorCaptured()` didn't return `false`,
// Vue will bubble up the error.
console.log('Caught top-level error', err.message);
++this.count;
return false;
},
template: `
<div>
<span id="count">{{count}}</span>
<level1></level1>
</div>
`
});
On the other hand, if your errorCaptured()
function returns false
, Vue will stop propagation of that error:
Vue.component('level2', {
template: '<button v-on:click="notAMethod()">Throw</button>'
});
Vue.component('level1', {
errorCaptured: function(err) {
console.log('Level 1 error', err.message);
return false;
},
template: '<level2></level2>'
});
const app = new Vue({
data: () => ({ count: 0 }),
errorCaptured: function(err) {
// Since the level1 component's `errorCaptured()` returned `false`,
// Vue won't call this function.
console.log('Caught top-level error', err.message);
++this.count;
return false;
},
template: `
<div>
<span id="count">{{count}}</span>
<level1></level1>
</div>
`
});