One sunny day, the interviewer met a young man. When introducing the project, the guy said that he had made an error reporting mechanism, and Vue’s error capture was used in the front end. At this time the interviewer glanced at the resume, a line of “familiar with Vue2 source code” hit the eye. After the introduction of the boy, the interviewer said that good good, then you talk about Vue’s error handling. Young man eyes a stare, wish this old iron not according to common sense card, say: this problem can’t! Next ~ Interviewer: Emmm… Okay, then the next…
After the interview, the guy immediately opened the Vue source code and decided to find out…
Understand Vue error handling
1. errorHandler
First, take a look at the Vue documentation. I don’t want to say too much here, just use it and look at the print results. The code is as follows:
// main.js
Vue.config.errorHandler = function (err, vm, info) {
console.log('Global capture err >>>', err)
console.log('Global capture VM >>>', vm)
console.log('Global capture info >>>', info)
}
// App.vue. created () {const obj = {}
// Try an error in the App component's Created hook directly, calling an fn that does not exist in obj
obj.fn()
},
methods: {
handleClick () {
// Bind a click event that fires when clicked
const obj = {}
obj.fn()
}
}
...
Copy the code
created
The output is as follows (this will be done at the end of the articlecatch
Process analysis) :
handleClick
The output is as follows (this will be done at the end of the articlecatch
Process analysis of
Thus it can be seen that:
err
Obtain error information and stack informationvm
Get the VM instance that reported the error (that is, the corresponding component)info
Specific error messages can be obtained. Such as life cycle informationcreated hook
, event informationv-on handler
2. errorCaptured
As always, you can look at the introduction of the Vue documentation first, and also put the use case directly here. The code is as follows:
// App.vue
<template>
// Reference the child HelloWorld in the template
<HelloWorld />
</template>
...
errorCaptured(err, vm, info) {
// Add errorCaptured hooks, as in the previous example
console.log('Parent component catches err >>>', err, vm, info)
}
...
/ / HelloWorld components. created () {const child = {}
// Create directly throws an error in the child component to see how it prints
child.fn()
}
...
Copy the code
The following output is displayed:As you can see,HelloWorld
An error in a component goes to the App componenterrorCaptured
Capture, also given globallyerrorHandler
Captured. Isn’t it a little bit like what happened in our caseThe bubbling?
It’s important to note that errorCaptured is called when catching an error from a descendant component, meaning that it cannot capture its own. To verify this, add errorCaptured hooks to HelloWorld and print “errorCaptured” in Created.
.created() {
console.log('Child components also catch errors with errorCaptured')
const child = {}
// Create directly throws an error in the child component to see how it prints
child.fn()
},
errorCaptured(err, vm, info) {
console.log('Subcomponent capture', err, vm, info)
}
...
Copy the code
Except for printing one more linecreated
, and nothing else changes.
3. A diagram summarizes the Vue error catching mechanism
Two, Vue error capture source code
The Vue version of the source code analysis is v2.6.14 and the code is located at SRC /core/util/error.js. There are four methods: handleError, invokeWithErrorHandling, globalHandleError, and logError. Let’s look at them one by one
1. handleError
The unified error handling function in Vue, which notifying errorHandler up to global errorHandler. The core interpretation is as follows:
- parameter
err
,vm
,info
pushTarget
,popTarget
. Note in the source code is written, mainly to avoid handling errorscomponentInfinite rendering$parent
. Attributes in the Vue component tree to establish parent-child relationships, which can be used to continuously look up the top-level component —The Vue
(the one we initialized with new Vue),The Vue
the$parent
是undefined
- To obtain
errorCaptured
. Some of you may wonder why it is hereAn array ofBecause the Vue initialization will do to the hookMerging processing. Let’s say we usemixins
The component may have multiple identical hooks, which will be initializedcb
都mergeIn an array of hooks to be called when the hooks are triggered capture
. If it is false, return will not go toglobalHandleError
中
The source code is as follows:
// Err, vm, info, etc
function handleError (err: Error, vm: any, info: string) {
pushTarget()
try {
if (vm) {
let cur = vm
// Look up $parent until it doesn't exist
// Pay attention! Cur is assigned to cur.$parent at first, so errorCaptured is not executed in error capture for the current component
while ((cur = cur.$parent)) {
// Get hooks errorCaptured
const hooks = cur.$options.errorCaptured
if (hooks) {
for (let i = 0; i < hooks.length; i++) {
try {
/ / errorCaptured execution
const capture = hooks[i].call(cur, err, vm, info) === false
ErrorCaptured returns false, and the outer globalHandleError is not executed
if (capture) return
} catch (e) {
// If errorCaptured is captured, globalHandleError is executed, and errorCaptured hook is info
globalHandleError(e, cur, 'errorCaptured hook')}}}}}// Global capture, as long as the above does not return, will be executed
globalHandleError(err, vm, info)
} finally {
popTarget()
}
}
Copy the code
2. invokeWithErrorHandling
A wrapper function that internally wraps the passed function with a try-catch and has better handling of asynchronous errors. Handles error catching for lifecycle, event, and other callback functions. An asynchronous error capture with a processable return value of Promise. When an error is caught, it is sent to handleError, which handles the logic for notification up to the global level. The core interpretation is as follows:
- parameter
handler
. The incoming execution function, called internally, makes a Promise on its return value try-catch
. Use try-catch to wrap and execute the function passed in, and call it when an error is caughthandleError
. (Isn’t it a little bitHigher-order functionsWhat about the smell?)handleError
. After catching an error, the handleError method is also called to notify the error upward
function invokeWithErrorHandling (
handler: Function,
context: any,
args: null | any[],
vm: any,
info: string
) {
let res
try {
// Handle the arguments of handle and call
res = args ? handler.apply(context, args) : handler.call(context)
// Check if the return is a Promise and no catch(! res._handled)
if(res && ! res._isVue && isPromise(res) && ! res._handled) { res.catch(e= > handleError(e, vm, info + ` (Promise/async)`))
// _handled flag is set to true to avoid multiple catches when nested calls are handled
res._handled = true}}catch (e) {
// Call handleError when an error is caught
handleError(e, vm, info)
}
return res
}
Copy the code
3. globalHandleError
Global error capture. This is the triggering function of vue.config. errorHandler that we configured globally
- Internal use
try-catch
The parcelerrorHandler
The execution. This is where our global error-catching function ~ is executed - If you execute
errorHandler
inerrorIs captured after passinglogError
Printing. (logError
Use in the browser production environmentconsole.error
Printing) - If there is no
errorHandler
. It will be used directlylogError
Error print
function globalHandleError (err, vm, info) {
if (config.errorHandler) {
try {
// Call global errorHandler and return
return config.errorHandler.call(null, err, vm, info)
} catch (e) {
If the user intentionally throws an original error in a handler, do not log it twice
if(e ! == err) {// Catch errors in globalHandleError, output by logError
logError(e, null.'config.errorHandler')}}}// If errorHandler is not captured globally, execute here with logError error
logError(err, vm, info)
}
Copy the code
4. logError
Print error messages (development environment, online will vary)
warn
. Warn is used in the development environment to print errors. In order to[Vue warn]:
At the beginningconsole.error
. Used in a browser environmentconsole.error
Output any errors caught
// logError source code implementation
function logError (err, vm, info) {
if(process.env.NODE_ENV ! = ='production') {
Warn is used in the development environment to output errors
warn(`Error in ${info}:"${err.toString()}"`, vm)
}
/* istanbul ignore else */
if ((inBrowser || inWeex) && typeof console! = ='undefined') {
// Directly print the error information with console.error
console.error(err)
} else {
throw err
}
}
// Take a quick look at the warn implementation
warn = (msg, vm) = > {
const trace = vm ? generateComponentTrace(vm) : ' '
if (config.warnHandler) {
config.warnHandler.call(null, msg, vm, trace)
} else if(hasConsole && (! config.silent)) {// This is where Vue warn usually prints errors!
console.error(`[Vue warn]: ${msg}${trace}`)}}Copy the code
Looking at the figure below, does the error output in the development environment look familiar if we don’t do global error catching? 👇
- Here’s a quick question: Why does one error print two error messages?
This is an implementation of the logError function. As a reminder, logError calls WARN to print a Vue wrapped error message starting with [Vue WARN]:, and then prints the JS error message through console.error
A quick summary:
handleError
: unified error trapping function. Implement sub-component to top-level component error capture post pairerrorCaptured
A bubbling call to a hook that executes a fullerrorCaptured
The global error-catching function globalHandleError is finally executed after the hook.invokeWithErrorHandling
: wrapping function, passHigher-order functionsThe private idea of programming by receiving a function argument and using it internallytry-catch
After the parcelperformFunction passed in; They offer better onesAsynchronous error handlingWhen an executing function returns a Promise object, its implementation is caught and notifiedhandleError
(if we don’t catch the returned Promise ourselves)globalHandleError
ErrorHandler: Calls the errorHandler function of the global configuration. If an error is caught during the call, it passeslogError
Prints the errors caught, ending with ‘config.errorHandler’logError
. Implements the printing of uncaptured error information. The development environment printsTwo kinds ofError message ~
Error capture process analysis
After looking at the source code implementation of error capture, it is better to take a look at how Vue catch errors, in order to deepen the understanding. There are many ways to catch a hit error. Here is the code case at the beginning of the article as a hit branch for debugging, take you to see how Vue is to achieve error capture ~
1. created
Phase error capture
Review what the entire componentization process (lifecycle) of Vue does, as shown below:
The trigger phase for created is in the init phase, as shown below:As you can see, the created hook is triggered bycallHook
Method, let’s seecallHook
The implementation of the:
- Traverses the current VM instanceThe currentHook all cb and pass it in
invokeWithErrorHandling
In the function invokeWithErrorHandling
Cb will be called, which will catch an error and executehandleError
. And now it’s in the App component, and then it goes upThe Vue
And already in useerrorHandler
Global error catching, so a series of console.log “global catches” are triggered.
function callHook (vm, hook) {
pushTarget();
var handlers = vm.$options[hook];
// Info, this is a created hook
var info = hook + " hook";
if (handlers) {
for (var i = 0, j = handlers.length; i < j; i++) {
// Call invokeWithErrorHandling directly, passing in the corresponding CB
invokeWithErrorHandling(handlers[i], vm, null, vm, info); }}if (vm._hasHookEvent) {
vm.$emit('hook:' + hook);
}
popTarget();
}
Copy the code
2. Error capture of click events
The example code is the same as the click for errorHandler in Vue error handling, but there is only one more line of console.log, so that you can see the packaged code for deeper understanding. This section refers to another point in the Vue source code — events. Of course, I’m not going to expand it out here, but you get the idea. I will write another chapter on Vue event source parsing ~
// Template code
<template>
<div id="app">
<button @click="handleClick">click</button>
</div>
</template>
/ / js code
methods: {
handleClick () {
console.log('Click event error capture')
const obj = {}
obj.fn()
}
}
Copy the code
The packaged code looks like this:Thus, during the entire Vue initialization process, the click event that we bound is performedupdateDOMListeners
Is then called toupdateListeners
This method, let’s look at itupdateListeners
What does the core code do?Here we do not have to go into the reason ha!! Just know the order in which this process is called, because post it so you can understand it a little more clearly. If you are interested in the author can wait for a Vue event source analysis ha ~
function updateListeners () {
// Cur is handleClick
cur = on[name] = createFnInvoker(cur, vm);
}
Copy the code
As you can see, our handleClick is wrapped and returned with createFnInvoker, where our error capture is implemented. So let’s see what createFnInvoker does, right
function createFnInvoker (fns, vm) {
function invoker () {
var arguments$1 = arguments;
// Get the method from invoker's static property FNS
var fns = invoker.fns;
if (Array.isArray(fns)) {
// A new array of FNS
var cloned = fns.slice();
for (var i = 0; i < cloned.length; i++) {
// Wrap FNS with invokeWithErrorHandling
invokeWithErrorHandling(cloned[i], null, arguments$1, vm, "v-on handler"); }}else {
// The same is true here, except that the single FNS is wrapped with invokeWithErrorHandling
return invokeWithErrorHandling(fns, null.arguments, vm, "v-on handler")}}// In this case, FNS, which is cur, is our handleClick method
invoker.fns = fns;
// Returns an invoker, which we clicked to trigger
return invoker
}
Copy the code
To sum up:
- Every time we click, the surface is triggered
handleClick
“Is actually triggering a decoratorinvoker
- Again by
invoker
To callinvokeWithErrorHandling
And pass in the saved in invokerStatic attributesFunctions in FNS (that is, we users writehandleClick
Function) - In this case, it’s likeTwo, Vue error capture source codeIn the
2. invokeWithErrorHandling
The execution is the same - Will eventually pass
handleError
Implement bubbling up the error hook of the upper component until global error capture
This is our error capture process for click events
How about at the end? Isn’t that easy? Error catching is important both at the framework level and in our daily development business, but often overlooked by many (like me). Overview down, in fact, this piece is not difficult, in the implementation of Vue source code, as long as we can understand. Anyway ~ learn a bit more not bad, the interview asked to don’t panic, though not the core focus of Vue interview, but pluses, can ask to answer must be, that if no answer at all, it could reduce it, especially in the project wrote a Vue error trapping related to, after all, this is a lot easier than reactive the ha ha ha ~