Vue 修复了 watch 的 BUG

学贯中西
• 阅读 1105

前言
在之前的项目中,需要做全局错误的收集和上报,最后有个头疼的问题就是 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 自己上报错误信息,或者这个有什么用?

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

对 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不胜感激 !

点赞
收藏
评论区
推荐文章
Wesley13 Wesley13
3年前
MySQL部分从库上面因为大量的临时表tmp_table造成慢查询
背景描述Time:20190124T00:08:14.70572408:00User@Host:@Id:Schema:sentrymetaLast_errno:0Killed:0Query_time:0.315758Lock_
美凌格栋栋酱 美凌格栋栋酱
7个月前
Oracle 分组与拼接字符串同时使用
SELECTT.,ROWNUMIDFROM(SELECTT.EMPLID,T.NAME,T.BU,T.REALDEPART,T.FORMATDATE,SUM(T.S0)S0,MAX(UPDATETIME)CREATETIME,LISTAGG(TOCHAR(
Wesley13 Wesley13
3年前
VBox 启动虚拟机失败
在Vbox(5.0.8版本)启动Ubuntu的虚拟机时,遇到错误信息:NtCreateFile(\\Device\\VBoxDrvStub)failed:0xc000000034STATUS\_OBJECT\_NAME\_NOT\_FOUND(0retries) (rc101)Makesurethekern
Wesley13 Wesley13
3年前
FLV文件格式
1.        FLV文件对齐方式FLV文件以大端对齐方式存放多字节整型。如存放数字无符号16位的数字300(0x012C),那么在FLV文件中存放的顺序是:|0x01|0x2C|。如果是无符号32位数字300(0x0000012C),那么在FLV文件中的存放顺序是:|0x00|0x00|0x00|0x01|0x2C。2.  
Wesley13 Wesley13
3年前
mysql设置时区
mysql设置时区mysql\_query("SETtime\_zone'8:00'")ordie('时区设置失败,请联系管理员!');中国在东8区所以加8方法二:selectcount(user\_id)asdevice,CONVERT\_TZ(FROM\_UNIXTIME(reg\_time),'08:00','0
Stella981 Stella981
3年前
Linux日志安全分析技巧
0x00前言我正在整理一个项目,收集和汇总了一些应急响应案例(不断更新中)。GitHub地址:https://github.com/Bypass007/EmergencyResponseNotes本文主要介绍Linux日志分析的技巧,更多详细信息请访问Github地址,欢迎Star。0x01日志简介Lin
Easter79 Easter79
3年前
SpringCloud项目,接口调用返回http 500
今天上班的时候,自己正在参与的SpringCloud项目出现了问题,原本上周五还正常的项目突然所有接口调用都是返回http500的错误。项目的状态是在Eureka上可以看到对应微服务是在线状态,然后在Swagger里面测试接口,发现接口间歇性调用失败,也就是题目中的http500的错误,如下图。至于是间歇性的原因在于这个服务在线上部署了一个,然后我
Stella981 Stella981
3年前
Hibernate纯sql查询结果和该sql在数据库直接查询结果不一致
问题:今天在做一个查询的时候发现一个问题,我先在数据库实现了我需要的sql,然后我在代码中代码:selectdistinctd.id,d.name,COALESCE(c.count_num,0),COALESCE(c.count_fix,0),COALESCE(c
Wesley13 Wesley13
3年前
NEO从源码分析看网络通信
_0x00前言_NEO被称为中国版的Ethereum,支持C和java开发,并且在社区的努力下已经把SDK拓展到了js,python等编程环境,所以进行NEO开发的话是没有太大语言障碍的。比特币在解决拜占庭错误这个问题时除了引入了区块链这个重要的概念之外,还引入了工作量证明(PoW)这个机智的解决方案,通过数学意义上的难题来保证每个
Stella981 Stella981
3年前
DevOps世界中的软件开发
!(https://oscimg.oschina.net/oscnet/f40e68cbfe8148deb00f040b4e917a0a.jpg)在整个软件开发过程中,开发人员通常需要花费大量时间来修复错误和漏洞,以便一切按计划进行交付。但是,通过DevOps实践,可以更轻松地管理和保护这些问题。这是由于以下事实:使用DevOps实践的软
Python进阶者 Python进阶者
1年前
Excel中这日期老是出来00:00:00,怎么用Pandas把这个去除
大家好,我是皮皮。一、前言前几天在Python白银交流群【上海新年人】问了一个Pandas数据筛选的问题。问题如下:这日期老是出来00:00:00,怎么把这个去除。二、实现过程后来【论草莓如何成为冻干莓】给了一个思路和代码如下:pd.toexcel之前把这