Vue fixed the BUG of watch

Keywords: Vue.js

preface
In previous projects, it was necessary to collect and report global errors. Finally, a headache was that asynchronous errors in Vue watch could not be reported to errorHandler. Then one day, when I read Vue code again, I found that he fixed this problem in version 2.6.13. Happy!!!

example
You can switch the version number of Vue to see the effect. You will find that < = 2.6.12 version of watch will not catch asynchronous errors

<!-- vue 2.6.12 -->
<script src="https://cdn.jsdelivr.net/npm/vue@2.6.12"></script>

<div id="app">
    <button @click='num++'>{{ num }}</button>
</div>

<script>
    Vue.config.errorHandler = (err, vm, info) => {
        console.log('Error collected:', err)
    }

    new Vue({
        el: '#app',
        data: { num: 100 },
        watch: {
            async num() {
                // await is added to catch asynchronous errors
                await this.errorFnc()
            }
        },
        methods: {
            errorFnc() {
                return new Promise((resolve, reject) => {
                    reject('promise error')
                })
            },
            // Or async function
            // async errorFnc() {
            //     throw 'async error'
            // },
        }
    })
</script>
Copy code

How is Vue resolved
2.6.12

Vue.prototype.$watch = function (
    expOrFn: string | Function,
    cb: any,
    options?: Object
): Function {
    const vm: Component = this
    if (isPlainObject(cb)) {
      return createWatcher(vm, expOrFn, cb, options)
    }
    options = options || {}
    options.user = true
    // The callback function executed in Watcher is the same as below, so the code will not be pasted
    const watcher = new Watcher(vm, expOrFn, cb, options)
    if (options.immediate) {
      try {
        // Execute callback function directly
        cb.call(vm, watcher.value)
      } catch (error) {
        handleError(error, vm, `callback for immediate watcher "${watcher.expression}"`)
      }
    }
    return function unwatchFn () {
      watcher.teardown()
    }
}
Copy code

2.6.13

Vue.prototype.$watch = function (
    expOrFn: string | Function,
    cb: any,
    options?: Object
): Function {
    const vm: Component = this
    if (isPlainObject(cb)) {
      return createWatcher(vm, expOrFn, cb, options)
    }
    options = options || {}
    options.user = true
    // The callback function executed in Watcher is the same as below, so the code will not be pasted
    const watcher = new Watcher(vm, expOrFn, cb, options)
    if (options.immediate) {
      const info = `callback for immediate watcher "${watcher.expression}"`
      pushTarget()
      // Use this function to execute the callback function
      invokeWithErrorHandling(cb, vm, [watcher.value], vm, info)
      popTarget()
    }
    return function unwatchFn () {
      watcher.teardown()
    }
}
Copy code

Comparison version
We found that the difference between the two versions is that the way of executing the callback function has changed. The callback function is executed through invokeWithErrorHandling. If it is promise, it will be caught and reported by handleError.

export function invokeWithErrorHandling (
    handler: Function,
    context: any,
    args: null | any[],
    vm: any,
    info: string
) {
    let res
    try {
        res = args ? handler.apply(context, args) : handler.call(context)
        if (res && !res._isVue && isPromise(res) && !res._handled) {
          res.catch(e => handleError(e, vm, info + ` (Promise/async)`))
          // issue #9511
          // avoid catch triggering multiple times when nested calls
          res._handled = true
        }
    } catch (e) {
        handleError(e, vm, info)
    }
    return res
}
Copy code

reflection
Some people may ask, why not try catch to report the error information yourself, or what's the use of this?

try catch yourself, and the repeated workload is huge.

This is a small repair for Vue, but for an online project, if you can't report all your errors, some places may affect the user experience, loss of users, and even loss of company property.

How Vue collects and reports errors
For our developers, it's better not to report errors manually, which will bring a lot of repetitive work. We'd better only focus on our normal business logic. For Vue project, Vue will automatically report our errors. As long as we ensure a certain writing method, the errors will not be lost.

First step
We only need one place to report errors globally, that is, Vue's errorHandler. Vue will report all errors to this function. You can directly apply sentry or call the background error reporting interface in this function.

Step 2
We have determined where to report errors. The next thing to do is to ensure that all errors can be captured by Vue, and the errors of synchronous tasks will be directly captured, while the errors of asynchronous tasks must be written in a certain way.

Asynchronous error
In the project, we mostly interact with the background, as follows

Writing method I
This is the most common writing method I have seen in the project. Once then is used to handle asynchronous tasks, it means that our errors will not be captured by Vue. If there are errors in our then callback function, we have to write a. catch at the end to capture the errors in the then callback function. This writing method increases a lot of workload for our developers.

mounted() {
    // No errors will be caught
    this.getData()
},
methods: {
    getData() {
        http.get('xxx').then(data => {
            // xxx
        }, error => {
            // You can only report asynchronous errors yourself
        })
    }
}
Copy code

Writing method 2
We only need to replace the writing of then with async await, and all errors will be caught and more concise

async mounted() {
    // Asynchronous errors can be caught using await
    await this.getData()
},
methods: {
    async getData() {
        const data = await http.get('xxx')
        // xxx
    }
}
Copy code

How to ensure that everyone develops using async syntax
If everyone in your project can follow this way, you don't have to look down.

For development projects, developers are uncontrollable and coding styles are ever-changing. Even if they remember which writing method, they will be negligent in actual development. If they can still be solved with tools, they don't need to be constrained verbally.

With eslint
Based on eslint, we can easily formulate a set of rules, but some rules are not available, so we need to develop them ourselves. I wrote a plug-in for the constraints of async syntax above: eslint plugin Leon rule. You can refer to the source code or use it.

last
If you think this article is a little helpful to you, give it a compliment. Or you can join my development exchange group: 1025263163 learn from each other, and we will have professional technical Q & A to solve doubts

If you think this article is useful to you, please click star: https://gitee.com/ZhongBangKeJi/CRMEB esteem it a favor!

Posted by easethan on Thu, 04 Nov 2021 03:31:20 -0700