@H_502_0@ 不管你的技术水平如何,错误或异常是应用程序开发者生活的一部分。Web开发的不连贯性留下了许多错误能够发生并确实已经发生的地方。解决的关键在于处理任何不可预见的(或可预见的错误),来控制用户的体验。利用JavaScript,就有多种技术和语言特色可以用来正确地解决任何问题。
在 JavaScript 中处理错误很危险。如果你相信墨菲定律,会出错的终究会出错!在这篇文章中,我会深入研究 JavaScript 中的错误处理。我会涉及到一些陷阱和好的实践。最后我们会讨论异步代码处理和 Ajax。
@H_502_0@ 我认为 JavaScript 的事件驱动模型给这门语言添加了丰富的含义。我认为这种浏览器的事件驱动引擎和报错机制没什么区别。每当发生错误,就相当于在某个时间点抛出一个事件。理论上说,我们在 JavaScript 中可以像处理普通事件一样去处理抛错事件。如果对你来说这听起来很陌生,那请集中注意力开始学习下面的旅程。本文只针对客户端的 JavaScript。
@H_5020@
示例
@H502_0@ 本文章中用到的代码示例在 GitHub 上可以得到,目前页面是这个样子的:
<p style="text-align: center"> @H_502_0@ 单击每个按钮都会引发一个错误。它模拟产生一个 TypeError 型的 exception。下面是对这样一个模块的定义及单元测试。
不好的示范
@H_502_0@ 先来看看不佳的错误处理方式。我处理错误的动作抽象出来,绑定在按钮上。下面是处理程序的单元测试的样子:it('returns a null with errors',function() {
var fn = function() {
throw Error('random error');
};
var result = target(fn);
should(result).equal(null);
});
@H_502_0@ 就像你看到的那样,如果发生了错误,这个诡异的处理方法会返回一个 null。这个回调函数 fn() 会指向一个合法的方法或者错误。下面的单击处理事件完成了剩下的部分。
var fn = function() {
throw Error('random error');
};
var result = target(fn);
should(result).equal(null);
});
if (badButton) {
badButton.addEventListener('click',function () {
handler(bomb);
console.log('Imagine,getting promoted for hiding mistakes');
});
}
}(badHandler,error));
@H_502_0@ 糟糕的是我刚刚得到的是个 null。这让我在想确定到底发生了什么错误的时候非常迷茫。这种发生错误就沉默的策略覆盖了从用户体验设计到数据损坏的各个环节。随之而来令人沮丧的一面就是,我必须花费好几个小时调试但是却看不到 try-catch 代码块里的错误。这种诡异的处理隐藏掉了代码中所有的报错,它假设一切都是正常的。这在某些不注重代码质量的团队中,能够顺利的执行。但是,这些被隐藏的错误最终会迫使你花几个小时来调试代码。在一种依赖于调用栈的多层解决方案中,有可能可以确定错误来自于何处。可能在极少数情况下对 try-catch 做故障静默处理是合适的。但是如果遇到错误就去处理,也不是一个好方案。
@H_502_0@ 这种失败即沉默的策略会促使你在代码中对错误做更好的处理。JavaScript 提供了更优雅的方式来处理这类问题。
@H_502_0@ badButton.addEventListener('click',function () {
handler(bomb);
console.log('Imagine,getting promoted for hiding mistakes');
});
}
}(badHandler,error));
不易读的方案
@H_502_0@ 继续,接下来来看看不太好理解的处理方式。我将会跳过与 DOM 紧耦合的部分。这部分与我们刚刚看过的不好的处理方式没什么不同。重点是下面单元测试中处理异常的部分。it('returns a new error with errors',function () {
var fn = function () {
throw new TypeError('type error');
};
should.throws(function () {
target(fn);
},Error);
});
@H_502_0@ 比起刚刚不好的处理方式,有一个很好的进步。异常在调用堆栈中被抛出。我喜欢的地方是错误从堆栈中解放出来,这对于调试有巨大的帮助。抛出一个异常,解释器就会在调用堆栈中一级级查看找到下一个处理函数。这就提供了很多机会在调用堆栈的顶层去处理错误。不幸的是,因为他是一种不太好理解的错误,我看不到了原始错误的信息。所以我必须沿着调用栈找过去,找到最原始的异常。但是至少我知道抛出异常的地方发生了一个错误。
@H_502_0@ 这种不易读的错误处理虽然无伤大雅但是却使得代码难以理解。让我们看看浏览器如何处理错误的。
@H_502_0@ var fn = function () {
throw new TypeError('type error');
};
should.throws(function () {
target(fn);
},Error);
});
调用栈
@H_502_0@ 那么,抛出异常的一种方式就是在调用堆栈的顶层添加 try...catch 代码块。比如说:记录下调用栈
@H_502_0@ 调用栈在处理修复 bug 上非常有用。好消息是浏览器提供了这个信息。就算目前,error 对象的 stack 属性并不是标准,但是在比较新的浏览器里都普遍支持这个属性。 @H_502_0@ 所以,我们能够做的很酷的事情就是把它给服务器打印出来:异步处理
@H_502_0@ 哦,处理异步代码相当危险!JavaScript 将异步代码从当前的执行环境中带出来。这意味着下面这种 try...catch 语句有个问题。FailedPromise(function() {
target(fn);
}).should.be.rejectedWith(TypeError);
});
@H_502_0@ 我必须用一个 promise 来结束这个处理程序,以验证异常。注意,尽管我的代码都在 try...catch 中,但是还是出现了未处理的异常。是的,try...catch 只在一个单独的执行环境中有作用。当异常被抛出时,解释器的执行环境已经不是当前的 try-catch 块了。这一行为的发生与 Ajax 调用相似。所以,现在有了两种选择。一种可选方案就是在异步回调中捕捉异常:
function FailedPromise(fn) {
return new Promise(function(resolve,reject) {
reject(fn);
});
}