《编写可维护的JavaScript》读书笔记之编程实践-抛出自定

2019-01-09  本文已影响5人  游学者灬墨槿

抛出自定义错误

在 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 new Error("Something bad happened.");

内置 Error 类型:

注意:

  1. 开发者可以抛出任何类型的数据,因为没有任何规则约束只能抛出指定的数据类型。但不是所有浏览器做出的响应都会按照你的预期。因此,针对所有的浏览器,唯一不出差错的显示自定义错误消息的方式——抛出 Error 对象。
// 不好的写法
throw "message";
throw { name : 'Nicholas' };
throw true;
throw 12345;
throw new Date();
  1. 如果没有通过 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 不满足继续执行的条件,就会抛出一个错误
 * 明确地陈述发生的问题。如果在浏览器控制台中输出
 * 该错误,开发者能马上开始调试,并知道最有可能导
 * 致该错误的原因。

【倾向】:抛出错误就像给自己留下告诉自己为什么失败的便签。

何时抛出错误

如何抛出错误只是技能水平上的掌握,理解什么时候抛出错误是思想层次上的要求,这是这一部分要讲的内容。

// 不好的写法
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 中这么做也
 * 会引起过度的杀伤。
 */

【关键】:辨识代码中哪些部分在特定的情况下最有可能导致失败,并只在那些地方抛出错误才是关键所在。

  1. 如果一个函数只被已知的实体调用,错误检查很可能没有必要。如果不能提前确定函数会被调用的所有地方,你很可能需要一些错误检查。
  2. 抛出错误最佳的地方是在工具函数中,如 addClass() 函数,它是通用脚本环境中的一部分,会在很多地方使用。更准确的案例是 JavaScript 类库。

【牢记】:我们的目的不是防止错误,而是在错误发生时能更加容易地调试。

try-catch 语句

JavaScript 提供了 try-catch 语句,能在浏览器处理抛出的错误前来解析它。

try {
    somethingThatMightCauseAnError();
} catch(ex) {
    handleError(ex);
} finally {
    continueDoingOtherStuff();
}
/*
 * 当在 try 块中发生一个错误时,程序立刻停止执行,
 * 然后跳到 catch 块,并传入一个错误对象。
 */

使用 throw 还是 try-catch:

通常开发者很难敏锐地判断是抛出一个错误还是用 try-catch 来捕获一个错误。

【建议】

  1. 错误只应该在应用程序栈中最深的部分抛出。任何处理应用程序特定逻辑的代码都应该有错误处理的能力,并且捕获从底层组件中抛出的错误。
  2. 应用程序逻辑总是调用某个特定函数的原因,因此也是最适合处理错误的。
  3. 千万不要将 try-catch 中的 catch 块留空,你应该总是写点什么来处理错误。

错误类型

ECMA-262 规范指出了 7 中错误类型。当不同错误条件发生时,这些类型在 JavaScript 引擎中都有用到,当然我们也可以手动创建它们。

【建议】

try {
    // 有些代码引发了错误
} catch(ex) {
    if(ex instanceof TypeError) {
        // 处理 TypeError 错误
    } else if(ex instanceof ReferenceError) {
        // 处理 ReferenceError 错误
    } else {
        // 其他处理
    }
}
function MyError(message) {
    this.message = message;
}
MyError.prototype = new Error();
/*
 * 该方法在 IE8 以及早期浏览器中不显示错误信息。
 * 但该方法最大的好处是,自定义错误类型可以检测
 * 自己的错误。
 */

try {
    // 有些代码引发了错误
} catch(ex) {
    if(ex instanceof MyError) {
        // 处理自己的错误
    } else {
        // 其他处理
    }
}
上一篇下一篇

猜你喜欢

热点阅读