抛出自定义错误
在 JavaScript 中抛出错误是一门艺术。一旦理解如何抛出错误,以及在何时抛出错误,调试代码的时间将大大所缩短,对代码的满意度将急剧提升。
错误的本质
如果错误没有被抛出或者报告给开发者,调试是非常困难的。如果所有的失败都是悄无声息,首要的问题是那必将消耗你大量的时间才能发现它,更不要说单独隔离并修复它了。所以错误是开发者的朋友,而非敌人。
【建议】:
在代码的某个特殊之处计划一个失败总比要在所有的地方都预期失败简单的多。
【心得】:
在函数调用链的深处抛出一个错误,可以阻止当前函数所在调用链上的所有上层函数。
function a() { b(); console.log("a"); }
function b() { c(); console.log("b"); }
function c() { throw new Error("执行异常"); }
a();
// 输出结果
Error:执行异常
在 JavaScript 中抛出错误
在 JavaScript 中抛出错误比在任何其他语言中做同样的事情更加有价值,这归咎于 Web 端调试的复杂性。
throw 操作符:
-
概述:
throw 操作符能够将提供的一个对象作为错误抛出。任何类型的对象都可以作为错误抛出。 -
示例:
throw new Error("Something bad happened.");
内置 Error 类型:
-
概述:
内置的 Error 类型在所有的 JavaScript 实现中都是有效的,它的构造器只接受一个参数,指代错误消息(message)。当以这种方式抛出错误时,如果没有通过 try-catch 语句来捕获的话,浏览器通常直接显示该消息(message 字符串)。当今大多数浏览器都有一个控制台,一旦发生错误都会在这里输出错误信息。换言之,任何你抛出的和没抛出的错误都被以相同的方式来对待。
注意:
- 开发者可以抛出任何类型的数据,因为没有任何规则约束只能抛出指定的数据类型。但不是所有浏览器做出的响应都会按照你的预期。因此,针对所有的浏览器,唯一不出差错的显示自定义错误消息的方式——抛出 Error 对象。
// 不好的写法
throw "message";
throw { name : 'Nicholas' };
throw true;
throw 12345;
throw new Date();
- 如果没有通过 try-catch 语句捕获,抛出任何值都将引发一个错误。
抛出错误的好处
抛出自定义的错误可以使用确切的描述文本提供给浏览器显示。除了行和列的号码,还可以包含任何你需要的有助于调试问题的信息。
【推荐】:总是在错误消息中包含函数名称,以及函数失败的原因。
function getDivs(element) {
return element.getElementsByTagName("div");
}
/*
* 传递给函数要操作的 DOM 为 null 可能是很常见的事情,
* 但实际需要的是 DOM 元素,这会造成错误。浏览器只会
* 报一个含糊的错误消息,你得去查找执行栈,再实际定位
* 源文件中的问题。因此抛出一个错误,调试会更简单。
*/
function getDivs(element) {
if(element && element.getElementsByTagName) {
return element.getElementsByTagName("div");
} else {
throw new Error("getDivs(): Argument must be a DOM element.");
}
}
/*
* 现在给 getDivs() 函数抛出一个错误,任何时候只要
* element 不满足继续执行的条件,就会抛出一个错误
* 明确地陈述发生的问题。如果在浏览器控制台中输出
* 该错误,开发者能马上开始调试,并知道最有可能导
* 致该错误的原因。
【倾向】:抛出错误就像给自己留下告诉自己为什么失败的便签。
何时抛出错误
如何抛出错误只是技能水平上的掌握,理解什么时候抛出错误是思想层次上的要求,这是这一部分要讲的内容。
-
情境:
由于 JavaScript 没有类型和参数检查,大量的开发者错误地假设他们应该实现每个函数的类型检查。这种做法并不实际,并且会对脚本的整体性能造成影响。 - 示例:
// 不好的写法
function addClass(element, className) {
if(!element || typeof element.className !== "string") {
throw new Error("addClass(): First argument must be a DOM element.");
}
if(typeof className !== "string") {
throw new Error("addClass(): Second argument must be a string.");
}
element.className += " " + className;
}
/*
* 这个函数本来只是简单地给一个给定的元素增加 CSS 类名,
* 而现在函数的大部分工作变成了错误检查。纵然它能在
* 每个函数中检查每个参数,在 JavaScript 中这么做也
* 会引起过度的杀伤。
*/
【关键】:辨识代码中哪些部分在特定的情况下最有可能导致失败,并只在那些地方抛出错误才是关键所在。
- 总结:
- 如果一个函数只被已知的实体调用,错误检查很可能没有必要。如果不能提前确定函数会被调用的所有地方,你很可能需要一些错误检查。
- 抛出错误最佳的地方是在工具函数中,如 addClass() 函数,它是通用脚本环境中的一部分,会在很多地方使用。更准确的案例是 JavaScript 类库。
-
经验法则:
- 一旦修复了一个很难调试的错误,尝试增加一两个自定义错误。当再次发生错误时,这将有助于更容易解决问题。
- 如果正在编写代码,思考一下:“我希望【某些事情】不会发生,如果发生,我的代码会一团糟糕”。这时,如果“某些事情”发生,就会抛出一个错误。
- 如果正在编写的代码他人(不知道是谁)也会使用,思考一下他们使用的方式,在特定的情况下抛出错误。
【牢记】:我们的目的不是防止错误,而是在错误发生时能更加容易地调试。
try-catch 语句
JavaScript 提供了 try-catch 语句,能在浏览器处理抛出的错误前来解析它。
-
try:放置可能引发错误的代码。
-
catch:放置处理错误的代码。
-
finally:该块中的代码不管是否有错误发生,最后都会被执行。
-
示例:
try {
somethingThatMightCauseAnError();
} catch(ex) {
handleError(ex);
} finally {
continueDoingOtherStuff();
}
/*
* 当在 try 块中发生一个错误时,程序立刻停止执行,
* 然后跳到 catch 块,并传入一个错误对象。
*/
-
注意:
在某些情况下,finally 块工作起来有点复杂。例如,如果 try 块中包含一个 return 语句,实际上它必须等到 finally 块中的代码执行后才能返回。
使用 throw 还是 try-catch:
通常开发者很难敏锐地判断是抛出一个错误还是用 try-catch 来捕获一个错误。
【建议】:
- 错误只应该在应用程序栈中最深的部分抛出。任何处理应用程序特定逻辑的代码都应该有错误处理的能力,并且捕获从底层组件中抛出的错误。
- 应用程序逻辑总是调用某个特定函数的原因,因此也是最适合处理错误的。
- 千万不要将 try-catch 中的 catch 块留空,你应该总是写点什么来处理错误。
错误类型
ECMA-262 规范指出了 7 中错误类型。当不同错误条件发生时,这些类型在 JavaScript 引擎中都有用到,当然我们也可以手动创建它们。
- Error:所有错误的基本类型。但实际上引擎从来不会抛出该类型的错误。
- EvalError:通过 eval() 函数执行代码发生错误时抛出。
- RangeError:超出边界时抛出错误。
- ReferencceError:引用不存在时抛出错误。
- SyntaxError:给 eval() 函数传递的代码中有语法错误时抛出。
- TypeError:变量不是期望的类型时抛出。
- URIError:给 encodeURI()、encodeURIComponent()、decodeURI()、decodeURIComponent() 等函数传递格式非法的 URI 字符串时抛出。
【建议】:
- 所有的错误类型都继承自 Error,所以用 instanceof Error 检查其类型得不到任何有用的信息。通过检查特定的错误类型可以更可靠地处理错误。
try {
// 有些代码引发了错误
} catch(ex) {
if(ex instanceof TypeError) {
// 处理 TypeError 错误
} else if(ex instanceof ReferenceError) {
// 处理 ReferenceError 错误
} else {
// 其他处理
}
}
- 创建自己的错误类型,让其继承自 Error。这种做法允许你提供额外的信息,同时区别于浏览器抛出的错误。
function MyError(message) {
this.message = message;
}
MyError.prototype = new Error();
/*
* 该方法在 IE8 以及早期浏览器中不显示错误信息。
* 但该方法最大的好处是,自定义错误类型可以检测
* 自己的错误。
*/
try {
// 有些代码引发了错误
} catch(ex) {
if(ex instanceof MyError) {
// 处理自己的错误
} else {
// 其他处理
}
}
网友评论