Skip to content

错误处理是框架开发过程中非常重要的环节。框架错误处理机制的好坏直接决定了用户应用程序的健壮性,还决定了用户开发时处理错误的心智负担。

为了让大家更加直观地感受错误处理的重要性,我们从一个小例子说起。假设我们开发了一个工具模块,代码如下:

js
export default {
  foo(fn) {
    fn && fn()
  }
}

该模块导出一个对象,其中 foo 属性是一个函数,接收一个回调函数作为参数,调用 foo 函数时会执行该回调函数,在用户侧使用时:

js
import utils from 'utils.js'

utils.foo(() => {
  // ...
})

大家思考一下,如果用户提供的回调函数在执行的时候出错了,怎么办?此时有两个办法,第一个办法是让用户自行处理,这需要用户自己执行 try...catch:

js
import utils from 'utils.js'

utils.foo(() => {
  try {
    // ...
  } catch (e) {
    // ...
  }
})

但是这会增加用户的负担。试想一下,如果 utils.js 不是仅仅提供了一个 foo 函数,而是提供了几十上百个类似的函数,那么用户在使用的时候就需要逐一添加错误处理程序。

第二个办法是我们代替用户统一处理错误,如以下代码所示:

js
export default {
  foo(fn) {
    try {
      fn && fn()
    } catch(e) {/* ... */}
  },
  bar(fn) {
    try {
      fn && fn()
    } catch(e) {/* ... */}
  },
}

在每个函数内都增加 try...catch 代码块,实际上,我们可以进一步将错误处理程序封装为一个函数,假设叫它 callWithErrorHandling:

js
export default {
  foo(fn) {
    callWithErrorHandling(fn)
  },
  bar(fn) {
    callWithErrorHandling(fn)
  },
}
function callWithErrorHandling(fn) { 
  try {
    fn && fn()
  } catch (e) {
    console.log(e)
  }
}

可以看到,代码变得简洁多了。但简洁不是目的,这么做真正的好处是,为开发者提供统一的错误处理接口,如以下代码所示:

js
let handleError = null
export default {
  foo(fn) {
    callWithErrorHandling(fn)
  },
  // 开发者可以调用该函数注册统一的错误处理函数
  registerErrorHandler(fn) {
    handleError = fn
  }
}
function callWithErrorHandling(fn) {
  try {
    fn && fn()
  } catch (e) {
    // 将捕获到的错误传递给开发者的错误处理程序
    handleError(e)
  }
}

我们提供了 registerErrorHandler 函数,用户可以使用它注册错误处理程序,然后在 callWithErrorHandling 函数内部捕获错误后,把错误传递给用户注册的错误处理程序。

这样用户侧的代码就会非常简洁且健壮:

js
import utils from 'utils.js'
// 注册错误处理程序
utils.registerErrorHandler((e) => {
  console.log(e)
})
utils.foo(() => {/* ... */})
utils.bar(() => {/* ... */})

这时错误处理的能力完全由用户控制,用户既可以选择忽略错误,也可以调用上报程序将错误上报给监控系统。

实际上,这就是 Vue.js 错误处理的原理,你可以在源码中搜索到 callWithErrorHandling 函数。另外,在 Vue.js 中,我们也可以注册统一的错误处理函数:

js
import App from 'App.vue'

const app = createApp(App)
app.config.errorHandler = () => {
  // 错误处理程序
}

如有转载或CV请标注本站原文地址