最佳的JavaScript错误处理实践

  不管你的技术水平如何,错误或异常是应用程序开发者生活的一部分。Web开发的不连贯性留下了许多错误能够发生并确实已经发生的地方。解决的关键在于处理任何不可预见的(或可预见的错误),来控制用户的体验。利用JavaScript,就有多种技术和语言特色可以用来正确地解决任何问题。
  在 JavaScript 中处理错误很危险。如果你相信墨菲定律,会出错的终究会出错!在这篇文章中,我会深入研究 JavaScript 中的错误处理。我会涉及到一些陷阱和好的实践。最后我们会讨论异步代码处理和 Ajax。

  我认为 JavaScript 的事件驱动模型给这门语言添加了丰富的含义。我认为这种浏览器的事件驱动引擎和报错机制没什么区别。每当发生错误,就相当于在某个时间点抛出一个事件。理论上说,我们在 JavaScript 中可以像处理普通事件一样去处理抛错事件。如果对你来说这听起来很陌生,那请集中注意力开始学习下面的旅程。本文只针对客户端的 JavaScript。

  示例

  本文章中用到的代码示例在 GitHub 上可以得到,目前页面是这个样子的:

  单击每个按钮都会引发一个错误。它模拟产生一个 TypeError 型的 exception。下面是对这样一个模块的定义及单元测试。

function error() {
 var foo = {};
 return foo.bar();
}

  首先,这个函数定义了一个空的对象 foo。请注意,bar() 方法没有在任何地方定义。我们用单元测试来验证这确实会引发报错。

it('throws a TypeError', function () {
 should.throws(target, TypeError);
});

  这个单元测试使用 Mocha 和 Should.js 库中的测试断言。Mocha 是一个运行测试框架,should.js 是一个断言库。如果你不太熟悉,可以在线免费浏览他们的文档。一个测试用例通常以 it('description') 开始,以 should 中断言的通过或者失败结束。用这套框架的好处就是可以在 node 里进行单元测试,而不必非在浏览器里。我建议大家认真对待这些测试,因为它们验证了 JavaScript 中很多关键的基本概念。

  如上所示, error() 定义了一个空对象,然后试图去调用其中的方法。因为在这个对象中不存在 bar() 这个方法,它会抛出一个异常。相信我,在像 JavaScript 这种动态语言里,任何人都有可能犯这类错误。

  不好的示范

  先来看看不佳的错误处理方式。我处理错误的动作抽象出来,绑定在按钮上。下面是处理程序的单元测试的样子:

function badHandler(fn) {
 try {
  return fn();
 } catch (e) { }
 return null;
}

  这个处理函数接收一个回调函数 fn 作为依赖。接着在处理程序的内部调用了这个函数。这个单元测试示例了如何使用这个方法。

it('returns a value without errors', function() {
 var fn = function() {
  return 1;
 };
 var result = target(fn);
 result.should.equal(1);
});

it('returns a null with errors', function() {
 var fn = function() {
  throw Error('random error');
 };
 var result = target(fn);
 should(result).equal(null);
});

  就像你看到的那样,如果发生了错误,这个诡异的处理方法会返回一个 null。这个回调函数 fn() 会指向一个合法的方法或者错误。下面的单击处理事件完成了剩下的部分。

(function (handler, bomb) {
 var badButton = document.getElementById('bad');

 if (badButton) {
  badButton.addEventListener('click', function () {
   handler(bomb);
   console.log('Imagine, getting promoted for hiding mistakes');
  });
 }
}(badHandler, error));

  糟糕的是我刚刚得到的是个 null。这让我在想确定到底发生了什么错误的时候非常迷茫。这种发生错误就沉默的策略覆盖了从用户体验设计到数据损坏的各个环节。随之而来令人沮丧的一面就是,我必须花费好几个小时调试但是却看不到 try-catch 代码块里的错误。这种诡异的处理隐藏掉了代码中所有的报错,它假设一切都是正常的。这在某些不注重代码质量的团队中,能够顺利的执行。但是,这些被隐藏的错误最终会迫使你花几个小时来调试代码。在一种依赖于调用栈的多层解决方案中,有可能可以确定错误来自于何处。可能在极少数情况下对 try-catch 做故障静默处理是合适的。但是如果遇到错误就去处理,也不是一个好方案。

  这种失败即沉默的策略会促使你在代码中对错误做更好的处理。JavaScript 提供了更优雅的方式来处理这类问题。

  不易读的方案

  继续,接下来来看看不太好理解的处理方式。我将会跳过与 DOM 紧耦合的部分。这部分与我们刚刚看过的不好的处理方式没什么不同。重点是下面单元测试中处理异常的部分。

function uglyHandler(fn) {
 try {
  return fn();
 } catch (e) {
  throw Error('a new error');
 }
}

it('returns a new error with errors', function () {
 var fn = function () {
  throw new TypeError('type error');
 };
 should.throws(function () {
  target(fn);
 }, Error);
});

  比起刚刚不好的处理方式,有一个很好的进步。异常在调用堆栈中被抛出。我喜欢的地方是错误从堆栈中解放出来,这对于调试有巨大的帮助。抛出一个异常,解释器就会在调用堆栈中一级级查看找到下一个处理函数。这就提供了很多机会在调用堆栈的顶层去处理错误。不幸的是,因为他是一种不太好理解的错误,我看不到了原始错误的信息。所以我必须沿着调用栈找过去,找到最原始的异常。但是至少我知道抛出异常的地方发生了一个错误。

  这种不易读的错误处理虽然无伤大雅但是却使得代码难以理解。让我们看看浏览器如何处理错误的。

  调用栈

  那么,抛出异常的一种方式就是在调用堆栈的顶层添加 try...catch 代码块。比如说:

function main(bomb) {
 try {
  bomb();
 } catch (e) {
  // Handle all the error things
 }
}

  但是,记得我说过浏览器是事件驱动的吗?是的,JavaScript 中的一个异常不过就是一个事件。解释器会在发生异常当前的上下文处停止程序,并抛出异常。为了证实这一点,下面写了一个我们能够看到的全局的事件处理函数 onerror。它看上去就是这个样子:

window.addEventListener('error', function (e) {
 var error = e.error;
 console.log(error);
});

  这个事件处理函数在执行环境中捕获错误。错误事件会在各种各样的地方产生各种错误。这种方式的重点是在代码中集中处理错误。就像其他的事件一样,你可以用一个全局的处理函数去处理各种不同的错误。这使得错误处理只有一个单一的目标,如果你遵守 SOLID (single responsibility 单一职责, open-closed 开闭, Liskov substitution 代换, interface segregation 界面分离 and dependency inversion 依赖倒置) 原则。你可以在任何时候注册错误处理函数。解释器会循环执行这些函数。代码从充满 try...catch 的语句中解放出来,变得易于调试。这种做法的关键是像处理 JavaScript 普通事件一样处理发生的错误。

  现在,有了一种方法,用全局处理函数来显示出调用栈,我们可以用它来做什么?终究,我们要利用调用栈。

  记录下调用栈

  调用栈在处理修复 bug 上非常有用。好消息是浏览器提供了这个信息。就算目前,error 对象的 stack 属性并不是标准,但是在比较新的浏览器里都普遍支持这个属性。

  所以,我们能够做的很酷的事情就是把它给服务器打印出来:

window.addEventListener('error', function (e) {
 var stack = e.error.stack;
 var message = e.error.toString();
 if (stack) {
  message += '\n' + stack;
 }
 var xhr = new XMLHttpRequest();
 xhr.open('POST', '/log', true);
 xhr.send(message);
});

  在代码示例中可能不太明显,但这个事件处理程序会被前面的错误代码触发。如上所述,每个处理程序都有一个单一的目的,它使代码 DRY(don't repeat yourself 不重复制造轮子)。我感兴趣的是如何在服务器上捕获这些消息。

  下面是 node 运行时的截图:

  调用堆栈对调试代码很有帮助。永远不要低估调用栈的作用。

  异步处理

  哦,处理异步代码相当危险!JavaScript 将异步代码从当前的执行环境中带出来。这意味着下面这种 try...catch 语句有个问题。

function asyncHandler(fn) {
 try {
  setTimeout(function () {
   fn();
  }, 1);
 } catch (e) { }
}

  这个单元测试还有剩下的部分:

it('does not catch exceptions with errors', function () {
 var fn = function () {
  throw new TypeError('type error');
 };
 failedPromise(function() {
  target(fn);
 }).should.be.rejectedWith(TypeError);
});

function failedPromise(fn) {
 return new Promise(function(resolve, reject) {
  reject(fn);
 });
}

  我必须用一个 promise 来结束这个处理程序,以验证异常。注意,尽管我的代码都在 try...catch 中,但是还是出现了未处理的异常。是的,try...catch 只在一个单独的执行环境中有作用。当异常被抛出时,解释器的执行环境已经不是当前的 try-catch 块了。这一行为的发生与 Ajax 调用相似。所以,现在有了两种选择。一种可选方案就是在异步回调中捕捉异常:

setTimeout(function () {
 try {
  fn();
 } catch (e) {
  // Handle this async error
 }
}, 1);

  这种方法虽然有用,但是还有很大的提升空间。首先,try...catch 代码块在代码中处处出现。事实上,上世纪 70 年代编程调用,他们希望他们的代码能够回退。另外,V8 引擎不鼓励 在函数中使用 try…catch 代码块 (V8 是 Chrome 浏览器和 Node 使用的 JavaScript 引擎)。他们推荐在调用堆栈顶层写这些捕获异常的代码块。

  所以,这告诉我们什么?我上面说过的,在任何执行上下文中的全局错误处理程序是有必要的。如果你将一个错误处理程序添加到 window 对象,那就是说,您已经完成了!遵守 DRY 和 SOLID 的原则不是很好吗?一个全局错误处理程序将保持你的代码易读和干净。

  下面就是服务器端异常处理打印的报告。注意,如果你使用的示例中的代码,输出的内容可能会根据你使用的浏览器不同有少许不同。

  这个处理函数甚至可以告诉我哪个错误是出自于异步代码。它告诉我错误来自于 setTimeout() 处理函数。太酷了!

  错误是每一个应用程序的一部分,但是适当的错误处理却不是。在处理错误这件事上至少有两种方法。一种是失败即沉默的方案,即在代码中忽略错误。另一种是快速发现和解决错误的方法,即在错误处停止并且重现。我想我已经把我赞成哪一种及为什么赞成表达地很清楚。我的选择:不要隐藏问题。没有人会为你程序中的意外事件去指责你。这是可以接受的,去打断点、重现、给用户一个尝试。在一个并不完美的世界中,给自己一个机会是很重要的。错误是不可避免的,为了解决错误你做的事情才是重要的。合理地运用JavaScript的错误处理特色和自动灵活的译码可以使用户的体验更顺畅,同时也让开发方的诊断工作变得更轻松。

(0)

相关推荐

  • JavaScript 错误处理与调试经验总结

    下面总结一下JS错误处理与调试的方法 方法1:用alert() 和document.write()方法监视变量值. alert()在弹出对话框显示变量值的同时,会停止代码的继续运行,直到用户单击"确定"按钮,而document.write()则在输出值后继续运行代码.调试JS时可以根据具体情况来选择这种方法. 例如下面代码:将数组a中以1开头的数据添加到数组b中 复制代码 代码如下: <!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.

  • 全面了解javascript中的错误处理机制

    前面的话 错误处理对于web应用程序开发至关重要,不能提前预测到可能发生的错误,不能提前采取恢复策略,可能导致较差的用户体验.由于任何javascript错误都可能导致网页无法使用,因此作为开发人员,必须要知道何时可能出错,为什么会出错,以及会出什么错.本文将详细介绍javascript中的错误处理机制 error对象 error对象是包含错误信息的对象,是javascript的原生对象.当代码解析或运行时发生错误,javascript引擎就会自动产生并抛出一个error对象的实例,然后整个程序

  • 深入分析javascript中的错误处理机制

    前面的话 错误处理对于web应用程序开发至关重要,不能提前预测到可能发生的错误,不能提前采取恢复策略,可能导致较差的用户体验.由于任何javascript错误都可能导致网页无法使用,因此作为开发人员,必须要知道何时可能出错,为什么会出错,以及会出什么错.本文将详细介绍javascript中的错误处理机制 error对象 error对象是包含错误信息的对象,是javascript的原生对象.当代码解析或运行时发生错误,javascript引擎就会自动产生并抛出一个error对象的实例,然后整个程序

  • JavaScript错误处理

    一.错误分类 1. 语法错误 也称为解析错误,发生在传统编程语言的编译时,在JavaScript中发生在解释时,这些错误是由代码中的意外字符直接引起的,然后就不能直接编译/解释,eg,在一行代码因缺少右括号,产生了语法错误.发生语法错误时,就不能继续执行代码.在JavaScript中,只有在同一个线程中的代码会受语法错误的影响.在其他线程中的代码和其他外部引用的文件中的代码,如果不依赖于包含错误的代码,则可以继续执行. 2. 运行时错误 也称为异常(exception,在编译期/解释器后).此时

  • Javascript 错误处理的几种方法

    1.使用window.onerror指定错误处理函数. 当有错误的时候,onerror会被callback. 当某个JavaScript block中有多个script错误时,第一个错误触发后(回调callback),当前Javascript block后面的script会被自动Drop忽略掉,不被执行. 如:  复制代码 代码如下: <html xmlns="http://www.w3.org/1999/xhtml"> <head> <title>

  • Javascript 学习笔记 错误处理

    Java代码 复制代码 代码如下: <html> <head> <title>javascript</title> <script type="text/javascript"> function test(inVal){ try{ inVal=inVal.toUpperCase(); }catch(error){ alert("An exception has occurred.Error was:\n\n&quo

  • javascript Error 对象 错误处理

    Error对象 Property:   name: 错误名   number: 错误号   description: 描述   message: 错误信息,多同description  FF Only 属性   fileName: 错误发生的文件   stack: 错误发生时的调用堆栈 Constructor:   Error(){     this(0,"")} Error(description){     this(0,description)} Error(number,des

  • 最佳的JavaScript错误处理实践

    不管你的技术水平如何,错误或异常是应用程序开发者生活的一部分.Web开发的不连贯性留下了许多错误能够发生并确实已经发生的地方.解决的关键在于处理任何不可预见的(或可预见的错误),来控制用户的体验.利用JavaScript,就有多种技术和语言特色可以用来正确地解决任何问题. 在 JavaScript 中处理错误很危险.如果你相信墨菲定律,会出错的终究会出错!在这篇文章中,我会深入研究 JavaScript 中的错误处理.我会涉及到一些陷阱和好的实践.最后我们会讨论异步代码处理和 Ajax. 我认为

  • javascript面向对象程序设计实践常用知识点总结

    本文实例讲述了javascript面向对象程序设计实践常用知识点.分享给大家供大家参考,具体如下: 实践一:原型中的引用类型的属性是共享的 var Person = function(){}; Person.prototype = { info:{ "name":"Tom" } } var p1 = new Person(); var p2 = new Person(); p1.info.name = '我是p1'; p2.info.name = '我是p2'; c

  • Dreamweaver 在onLoad运行***,发生了一个JavaScript错误

    错误提示类似: '在onChange运行Flash Button.htm时,发生了一个javascript错误'  在onLoad运行Design Notes_onOpen.htm时,发生了一个JavaScript错误. "在applyServerbahvior运行 InsertRecord.htm时发生了一个Javascript错误" 删除C:\Documents and Settings\Administrator\Application Data\Macromedia\Dreamweave

  • 10 种最常见的 Javascript 错误(频率最高)

    为了回馈我们的开发者社区,我们查看了数千个项目的数据库,发现了 JavaScript 中频度最高的 10 种错误.我们会告诉你什么原因导致了这些错误,以及如何防止这些错误发生.如果你能够避免落入这些 "陷阱",你将会成为一个更好的开发者. 数据才是王道,我们收集并分析了出现频次排前 10 的 JavaScript 错误. Rollbar 会收集每个项目的所有错误,并总结每个错误发生的次数.我们通过根据 "指纹"(rollbar 用到的一种算法,详见:https://

  • JavaScript错误处理操作实例详解

    本文实例讲述了JavaScript错误处理操作.分享给大家供大家参考,具体如下: 良好的错误处理机制可以让用户得到及时的提醒,所以让我们来看看 JavaScript 提供了哪些针对错误处理的工具和方法吧O(∩_∩)O~ 1 try-catch 语句 ECMA-262 第 3 版引入了 try-catch 语句,这时 JavaScript 处理异常的标准方式: try{ //可能会导致错误的代码 } catch (error){ //错误处理 } 如果 try 块中的代码发生了错误,会立即执行 c

  • 简介JavaScript错误处理机制

    1. try-catch语句 ECMA-262第3版引入了try-catch语句,作为JavaScript中处理异常的一种标准方式. 语法: try{ // 可能会导致错误的代码 }catch (error){ // 在错误发生时怎么处理 } 也就是说,我们应该把所有可能会抛出错误的代码都放在try语句块中,而把那些用于错误处理代码放在catch块中. try-catch语句的逻辑是:如果try块中的任何代码发生了错误,就会立即退出代码执行过程,然后接着执行catch块.此时,catch块会接收

  • 详解JavaScript错误捕获

    目录 一.基本使用与逻辑 二.特性 三.错误对象 四.较好的catch和throw策略 五.Promise的错误处理 六.性能损耗​ 一.基本使用与逻辑 使用 try{ //code.... }catch(err){ //error handling }finally{ //no matter what happens in the try/catch (error or no error), this code in the finally statement should run. } 逻辑

  • JavaScript错误处理try..catch...finally+涵盖throw+TypeError+RangeError

    目录 1.用途 2.语法 3.实操 1.用途 通常,如果发生错误,脚本就会立即停止,并在控制台将错误打印出来. 有了这个语句就可以捕获错误并执行合理操作,可以让程序继续执行下去 2.语法 try { // 代码... } catch (err) { //err是有关错误详细信息的对象 // 错误捕获,上面代码报错就会转到这个代码块,而不会停止运行 } finally { //无论是否有异常抛出或捕获它总是执行 } 这种语句可以嵌套 3.实操 Catch 捕获所有 error. 如果我们不知道如何

  • JavaScript错误处理机制全面分析讲解

    目录 1. Error 实例 2. 原生错误类型 2.1 ReferenceError 2.2 SyntaxError 2.3 TypeError 2.4 RangeError 2.5 URIError 2.6 evalError 3. 自定义错误类型 4. throw 5. try…catch 6. finally 总结 1. Error 实例 JavaScript在运行错误时会抛出一个错误,JS提供了Error构造函数,所有抛出的错误都是这个构造函数的实例 const err = new E

  • Go 错误处理实践总结示例

    目录 前言 Go 错误处理机制 Go 内置 errors Error 与 Exception Go 错误处理最佳实践 panic error 总结 前言 最近在对极客时间毛剑老师的 Go 进阶训练营进行重温和学习汇总,这是一门比较偏向于工程化以及原理层面的的课程,涵盖的知识点非常多,因此决定开一个系列来进行记录,也便于自己总结查阅. Go 错误处理机制 Go 内置 errors Go 语言中的 error 就是普通的一个接口,表示值 // http://golang.org/pkg/builti

随机推荐