不管你的技术水平如何,错误或异常是应用程序开发者生活的一部分。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 提供了更优雅的方式来处理这类问题。
不易读的方案