您的位置:  首页 > 技术 > 前端 > 正文

Vue 修复了 watch 的 BUG

2021-11-04 13:00 https://my.oschina.net/u/5168284/blog/5293021 CRMEB众邦科技 次阅读 条评论
 

前言

在之前的项目中,需要做全局错误的收集和上报,最后有个头疼的问题就是 Vue watch 中的异步错误无法上报到 errorHandler 里面,然后在某一天我再次阅读 Vue 代码的时候,发现他在 2.6.13 版本上修复了这个问题,开心!!!

 

例子

大家可以切换 Vue 的版本号,来看看效果,你会发现 <= 2.6.12 版本的 watch 都不会捕获到异步错误

<!-- 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('收集到错误:', err)

    }



    new Vue({

        el: '#app',

        data: { num: 100 },

        watch: {

            async num() {

                // 加 await 是为了捕获异步错误

                await this.errorFnc()

            }

        },

        methods: {

            errorFnc() {

                return new Promise((resolve, reject) => {

                    reject('promise 错误')

                })

            },

            // 或者 async 函数

            // async errorFnc() {

            //     throw 'async 错误'

            // },

        }

    })

</script>

复制代码
   

Vue 是如何解决的

 

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

    // Watcher 里面执行回调函数和下面一样,就不贴代码了

    const watcher = new Watcher(vm, expOrFn, cb, options)

    if (options.immediate) {

      try {

        // 直接执行回调函数

        cb.call(vm, watcher.value)

      } catch (error) {

        handleError(error, vm, `callback for immediate watcher "${watcher.expression}"`)

      }

    }

    return function unwatchFn () {

      watcher.teardown()

    }

}

复制代码
   

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

    // Watcher 里面执行回调函数和下面一样,就不贴代码了

    const watcher = new Watcher(vm, expOrFn, cb, options)

    if (options.immediate) {

      const info = `callback for immediate watcher "${watcher.expression}"`

      pushTarget()

      // 用该函数去执行回调函数

      invokeWithErrorHandling(cb, vm, [watcher.value], vm, info)

      popTarget()

    }

    return function unwatchFn () {

      watcher.teardown()

    }

}

复制代码
   

对比版本

我们发现两个版本不同的是执行回调函数的方式变了,通过 invokeWithErrorHandling 执行回调函数,如果是 promise 的话会被 catch,从而被 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

}

复制代码
   

思考

有人可能会问,为什么不 try catch 自己上报错误信息,或者这个有什么用?

  1. 自己 try catch,重复工作量极大。

  2. 对 Vue 来说这个是一个很小的修复,但对于一个线上项目来说,如果无法上报你的所有错误,那么有些地方就可能会影响到用户体验,产生用户的流失,甚至让公司财产损失。

 

Vue 如何进行错误收集和上报

对于我们开发者来说,最好是不用手动上报错误,这会带来很多重复性的工作,我们最好只用关注我们的正常的业务逻辑,而对于 Vue 工程来说,Vue 会自动上报我们的错误,我们只要保证一定的写法,错误就不会丢失。

 

第一步

我们全局只需要一个上报错误的地方,那就是 Vue 的 errorHandler,Vue 会把所有错误上报到这个函数,你可以直接应用 sentry,或者在这个函数里面调用后台的错误上报接口。

 

第二步

我们确定了上报错误的地方,下面要做的就是保证所有错误能被 Vue 捕获到,同步任务的错误会被直接捕获,而异步任务的错误,我们必须使用一定的写法。

 

异步错误

项目中我们最多的是和后台交互,如下

 

写法一

这个是我在项目中见的最多的写法,一旦使用了 then 来处理异步任务,就意味着我们的错误不会被 Vue 捕获,如果我们 then 回调函数里面出现了错误,我们还得在最后面写一个 .catch 来捕获 then 回调函数里面的错误,这种写法给我们开发者加大了很多的工作量。

mounted() {

    // 不会捕获到错误

    this.getData()

},

methods: {

    getData() {

        http.get('xxx').then(data => {

            // xxx

        }, error => {

            // 只能自己上报异步错误

        })

    }

}

复制代码
   

写法二

我们只用换成 async await 来替代我们 then 的写法,所有错误就会被捕获到,而且更加简洁

async mounted() {

    // 使用 await 可以捕获到异步错误

    await this.getData()

},

methods: {

    async getData() {

        const data = await http.get('xxx')

        // xxx

    }

}

复制代码
   

如何保证所有人使用 async 语法开发

如果你的项目中大家都可以遵守这种写法,那就不用往下看了。

对于开发项目来说,开发者是不可控的,编码风格也是千变万化,而且就算记住了哪种写法,在实际开发的时候也会有疏忽,还是能用工具解决的就不用口头去约束。

 

借助 eslint

基于 eslint 我们可以很轻松制定一套规则,但有一些规则是没有的,就需要我们自己开发,我对上面 async 语法的约束写了一个插件:eslint-plugin-leon-rule,大家可以参考下源码或者使用。

 

最后

如果你觉得此文对你有一丁点帮助,点个赞。或者可以加入我的开发交流群:1025263163相互学习,我们会有专业的技术答疑解惑

如果你觉得这篇文章对你有点用的话,麻烦请给我们的开源项目点点star: https://gitee.com/ZhongBangKeJi/CRMEB不胜感激 !

  • 0
    感动
  • 0
    路过
  • 0
    高兴
  • 0
    难过
  • 0
    搞笑
  • 0
    无聊
  • 0
    愤怒
  • 0
    同情
热度排行
友情链接