前端错误与捕获

523 阅读4分钟

原文链接: jasperjia.github.io

不同于运行在服务器端的程序,前端大量的服务运行在客户端。由于用户错误现场可能很难去复现,以及用户设备的多样性,导致错误的收集和处理成为一个非常棘手的难题。一套完善的错误处理机制此时是必不可少的。

Error 对象

Error对象是一个经常被前端初学者忽略的JavaScript内建对象。当JavaScript运行错误产生时候,会抛出Error对象的实例。用户可以使用Error对象自定义异常,同时JavaScript提供了几种内置的错误类型。

Error

通用的Error对象,可以用于自定义异常

RangeError

数值变量或参数超出有效范围,主要包括以下两种类型:

  1. 使用Array构造器方法创建数组时候,传递了一个非法的length值;
  2. 传递错误值到数值计算方法,例如Number.toFixed()
ReferenceError

无效应用,例如访问未定义的变量。

TypeError

变量或参数不属于有效类型,也就是传入函数的操作数和参数的类型并非操作符和函数所语预期的类型。例如调用一个不是函数的变量。

SyntaxError ['sɪntæks]

语法错误,eval()在解析代码过程中发生错误

URIError

decodeURL()encode()参数非法

InternalError

JavaScript 引擎内部错误(递归过深),尚未被纳入任何规范,不要在生产环境中使用。

EvalError

eval()有关的错误,此异常已经不会被JavaScript抛出。

JavaScript在运行时发生以上几种错误,就会抛出对应类型的实例对象。可以使用instanceof判断错误类型。以上错误对象的实例均包含了以下属性:

  1. message 错误信息
  2. name 错误类型名称
  3. fileName 引发错误的文件路径
  4. lineNumber 错误所在行
  5. columnNumber 错误所在列
  6. stack 堆栈

通过获取不同的错误类型和相关信息,有助于开发者对错误进行恰当合适的处理。

错误捕获

在网页应用运行时发生的错误,我们需要能够及时准确地捕获、收集和上报。一套错误处理机制是否完善,应该从两个方面去判断和衡量。一方面是否提升用户体验,使程序的异常对用户更加友好;另一方面是否有利于开发人员迅速发现出现异常的地方,进行及时修复。其中,如何对错误进行捕获,是我们首要面临的问题。 JavaScript中对错误的捕获,主要提供了两种方式。

window.onerror

发生以下类型的错误,都会出发error事件。

  • 当JavaScript运行时发生错误,window会 触发error事件,并执行window.onerror事件句柄;
  • 外部资源(常见的有<img><script>),加载资源的元素会出发error事件,并执行该元素的onerror事件句柄。

加载一个全局的error事件处理函数可用于自动收集错误报告。

window.onerror = (message, source, lineNo, colNo, error) => {
    // 处理错误
    console.warn(`some error: ${message}`);
}

函数参数:

  • message:错误信息
  • source:发生错误的脚本URL
  • lineNo:发生错误的行号
  • colNo:发生错误的列号
  • error:Error对象

若函数返回true,会阻止默认的事件处理函数。 但是这种方式还存在一些问题:

  1. 引用的不同域资源发生的错误,这类error事件不会冒泡到window,所以无法在window.onerror中捕获。
  2. 不能捕获网络异常。
  3. 不能捕获Promise、async/await等异步代码抛出的错误。

对于上述问题,对应的解决方法:

外部资源错误

对于网络异常导致的外部资源错误,我们可以在事件捕获的阶段获取,可以用我们熟知的;

window.addEventListener('error',(event) => {
  event.stopImmediatePropagation();
  const { srcElement } = event;
  if(srcElement === window){
    // 处理全局错误
    console.warn(event.message);
  }else{
    // 处理引用资源错误
    console.warn(srcElement.tagName);
  }
},true);
Promise错误

我们通常可以在Promise实例中通过以下两种回调的方式进行捕获;

  • 通过reject回调函数捕获。
  • 通过catch回调函数捕获。
let errorPro = new Promise((resolve, reject) => {
  console.log(foo); 
  reject('foo is not defined');
});

errorPro.catch(err => {
  console.warn(`通过catch捕获:${err}`);
}).then(res => {}, err => {
  console.warn(`通过reject捕获:${err}`);
});

如果Promise实例发生错误被reject但是没有捕获处理,会触发unhandledrejection事件。

window.addEventListener(`unhandledrejection`, (event) => {
  console.warn(`unhandled rejection: ${event.reason}`);
});
async/await错误

async/await产生的同步或异步的错误一般可以通过在async中使用try/catch语句进行捕获。

function getData(){
  return new Promise((resolve, reject) => {
    // 抛出异常
    throw new Error('error');
  });
}

(async function foo(){
  try{
    await getData();
  }catch(err){
    // 处理错误
    console.warn(err);
  }
})();

try/catch

我们也可以手动将可能引发错误的代码放在try/catch语句中,在异常被抛出时执行相应操作。同时,try/catch语句也无法捕获异步代码抛出的错误。

try {
   throw new Error('error');
} catch (error) {
    console.warn('some error: ${error}');
}

总结

对于前端代码运行中出现的错误,我们可以通过以下方式进行捕获:

  1. 在可能发生错误的地方使用try/catch语句。
  2. 使用window.onerror事件处理错误,可以捕获try/catch中没有的捕获到的所有错误。

这里仅仅简单介绍常见的前端错误基础知识和如何捕获前端错误。对于如何建立一套完善的前端异常监控机制,将在随后的文章中具体阐述。

参考资料