JavaScript异常错误处理指南

交互设计

  在前端的 JavaScript 开发中,发现开发者对于错误异常的处理普遍都比较简单粗暴,如果应用程序中缺少有效的错误处理和容错机制,代码的健壮性就无从谈起。本文整理出了一些常见的错误异常处理的场景,旨在为前端的 JavaScript 错误异常处理提供一些基础的指导。

  Error 对象

  先来简单介绍一下 JavaScript 中的 Error 对象,通常 Error 对象由重要的两部分组成,包含了 error.message 错误信息和 error.stack 错误追溯栈。

  产生一个错误很简单,比如在 foo.js 中直接调用一个不存在的 callback 函数。

  // foo.jsfunction foo () { callback();}foo();此时通过 Chrome 浏览器的控制台会展示如下的信息。

  Uncaught ReferenceError: callback is not defined at foo (foo.js:2) at foo.js:5其中 Uncaught ReferenceError: callback is not defined 就是 error.message 错误信息,而剩下的 at xxx 就是具体的错误追溯栈,在 Chrome 的控制台中,对错误的展示进行了优化。

  如果我们通过 window.onerror 来捕获到该错误后将 Error 对象直接输出到页面中会展示出更原始的数据。

  <!-- 展示错误的容器 --><textarea id="error"></textarea>// 输出错误window.onerror = function (msg, url, line, col, err) { document.getElementById('error').textContent = err.message + '\n\n' + err.stack;};原始的错误数据中会展示出错误追溯栈中的 Source URL。

  callback is not definedReferenceError: callback is not defined at foo (http://example.com/js-error/foo.js:2:5) at http://example.com/js-error/foo.js:5:1有了错误追溯栈,就能通过发生错误的文件 Source URL 和错误在代码中的具体位置来快速定位到错误。

  看起来好像很简单,但实际的开发中如何有效的捕获错误,如何有效的抛出错误都有一些需要注意的点,下面逐个的来讲解。

  window.onerror

  前端在捕获错误时都会通过绑定 window.onerror 事件来捕获全局的 JavaScript 执行错误,标准的浏览器在响应该事件时会依次提供 5 个参数。

  window.onerror = function(message, source, lineno, colno, error) { ... }message 错误信息source 错误发生时的页面 URLlineno 错误发生时的 JS 文件行数colno 错误发生时的 JS 文件列数error 错误发生时抛出的标准 Error 对象使用 window.addEventListener 也能绑定 error 事件,但是该事件函数的参数是一个 ErrorEvent 对象。

  绑定 window.onerror 事件时,事件处理函数的第 5 个参数在低版本浏览中或 JS 资源跨域场景下可能不是 Error 对象。

  在 Chrome 浏览器中如果页面加载的 JS 资源文件中存在跨域的 script 标签,在发生错误时会提示 Script error 而缺乏错误追溯栈。

  window.onerror 在响应跨域 JavaScript 错误时缺乏错误追溯栈时的 arguments 对象如下:

  [ 'Script error.', '', 0, 0, null]为了正常的捕获到跨域 JS 资源文件的错误,需要具备两个条件: 1. 为 JS 资源文件增加 CORS 响应头。 2. 通过 script 引用该 JS 文件时增加 crossorigin="anonymous" 的属性,如果是动态加载的 JS,可以写作 script.crossOrigin = true 。

  window.onerror 能捕获一些全局的 JavaScript 错误,但还有不少场景在全局是捕获不到的。

  try/catch

  window.onerror 能捕获全局场景下的错误,如果已知一些程序的场景中可能会出现错误,这个时候一般会使用 try/catch 来进行捕获。

  但是在使用 try/catch 块时无法捕获异步错误,例如块中使用了 setTimeout 。

  try { setTimeout(function () { callTimeout(); // callTimeout 未定义,会抛错 }, 1000);}catch (err) { console.log('catch the error', err); // 不会被执行}try/catch 在处理 setTimeout 这类异步场景时是无效的,执行时仍会抛错,catch 中的代码不会被执行。

  虽然在 try/catch 中没有捕获到,此时如果有绑定 window.onerror 则会被全局捕获。

  由此可见, try/catch 应该是只能捕获 JS Event Loop 中同步的任务。

  如果想正确的捕获 setTimeout 中的错误,需要将 try/catch 块写到 setTimeout 的函数中。

  setTimeout(function () { try { callTimeout(); // callTimeout 未定义,不会抛错 } catch (err) { console.log('catch the error', err); // 将会被执行 }}, 1000);Promise

  Promise 有自己的错误处理机制,通常 Promise 函数中的错误无法被全局捕获。

  var promise = new Promise(executor);promise.then(onFulfilled, onRejected);比较容易遗漏错误处理的地方有 executor 和 onFulfilled ,在这些函数中如果发生错误都不能被全局捕获。

  正确的捕获 Promise 的错误,应该使用 Promise.prototype.catch 方法,意外的错误和使用 reject 主动捕获的错误都会触发 catch 方法。

  catch 方法中通常会接收到一个 Error 对象,但是当调用 reject 函数时传入的是一个非 Error 对象时,catch 方法也会接收到一个非 Error 对象,这里的 reject 和 throw 的表现是一样的,所以在使用 reject 时,最好是传入一个 Error 对象。

  reject( new Error('this is reject message'));值得注意的是,如果 Promise 的 executor 中存在 setTimeout 语句时, setTimeout 的报错会被全局捕获。

  Async Function

  Async Function 和 Promise 一样,发生错误不会被全局的 window.onerror 捕获,所以在使用时如果有报错,需要手动增加 try/catch 语句。

  匿名函数

  匿名函数的使用在 JavaScript 中很常见,但是当出现匿名函数的报错时,在错误追溯栈中会以 anonymous 来标识错误,为了排查错误方便,可以将函数进行命名,或者使用函数的 displayName 属性。

  函数如果有 displayName 属性,在错误栈中会展示该属性值,如果用于命名重要的业务逻辑属性,将有效帮助排查错误。

  throw error

  上面说了很多错误捕获的注意点,如果要主动的抛错,都会使用 throw 来抛错,常见的几种抛错方法如下:

  throw new Error('Problem description.') // 方法 1throw Error('Problem description.') // 方法 2throw 'Problem description.' // 方法 3throw null // 方法 4其中方法 1 和方法 2 的效果一样,浏览器都能正确的展示错误追溯栈。方法 3 和方法 4 不推荐,虽然能抛错,但是在抛错的时候不能展示错误追溯栈。

  try/catch 和 throw ,一个用来捕获错误,一个用来抛出错误,如果两个结合起来用通常等于脱了裤子放屁多此一举,唯一有点用的是可以对错误信息进行再加工。

  可以在 Chrome 控制台中模拟出一个结合使用的实际场景。

  try { foo();}catch (err) { err.message = 'Catch the error: ' + err.message; throw Error(err);}由于在 catch 块中又抛出了错误,所以该错误没有被捕获到,但此时错误信息经过了二次封装。

  Uncaught Error: ReferenceError: Catch the error: foo is not defined通过对错误信息的二次封装,可以增加一些有利于快速定位错误的额外信息。

  原作者:雨夜带刀's Blog

标签: 交互设计