JS高程:读书摘要(十五)错误处理

一、 try-catch语句

try {
    window.someNonexistentFunction();
} catch (error){
    alert(error.message);
}

如果try块中的任何代码发生了错误,就会立即退出代码执行过程,然后接着执行catch块。此时,catch块会接收到一个包含错误信息的对象。即使你不想使用这个错误对象,也要给它起个名字。这个对象中包含的实际信息会因浏览器而异,但共同的是有一个保存着错误消息的message属性

finally 子句

finally子句是可选的,但是一经使用必然执行(无论try语句块中的代码执行正常或者执行错误跳到catch块中都不会组织finally子句的执行),如果提供finally子句,则catch子句就成了可选的(catchfinally有一个即可)。

function testFinally(){
    try {
        return 2;
    } catch (error){
        return 1;
    } finally {
        return 0;
    }
}

调用这个函数会返回0,因为最后还有一个finally子句,结果就会导致try语句块的return语句被忽略;也就是说,调用这个函数只能返回0

错误类型
  • Error:基类型,其他错误类型都继承自该类型。
  • EvalError:会在使用eval()函数而发生异常时被抛出。没有正确的调用或者给eval属性赋值。
  • RangeError:会在数值超出相应范围时触发。例如:指定了数组不支持的项数(如-20Number.MAX_VALUE
  • ReferenceError:引用错误,在访问不存在的变量时,就会发生这种错误。
  • SyntaxError:语法错误,如果在定义一个函数时遗漏了闭合花括号 (}),则触发了一个语法错误。
  • TypeError:在变量中保存着意外的类型时,或者在访问不存在的方法时,都会导致这种错误。
  • URIError:在使用encodeURI()decodeURI(),而URI格式不正确时,就会导致URIError错误。

利用不同的错误类型,可以获悉更多有关异常的信息,从而有助于对错误作出恰当的处理。

try {
    someFunction();
} catch (error){
    if (error instanceof TypeError){
        //处理类型错误
    } else if (error instanceof ReferenceError){
        //处理引用错误
    } else {
        //处理其他类型的错误
    }
}
合理使用try-catch

try-catch语句中发生错误时,浏览器会认为错误已经被处理了,因而不会向用户报告错误。

如果你在使用一个大型JavaScript库中的函数,该函数可能会有意无意地抛出一些错误。由于我们不能修改这个库的源代码,所以大可将对该函数的调用放在try-catch语句当中,万一有什么错误发生,也好恰当地处理它们(无法控制的错误)。

如果传递给函数的参数是字符串而非数值,就会造成函数出错,那么就应该先检查参数的类型,然后再决定如何去做。在这种情况下,不应用使用try-catch语句。

二、抛出错误

try-catch语句相配的还有一个throw操作符,用于随时抛出自定义错误。抛出错误时,必须要给throw操作符指定一个值,这个值是什么类型,没有要求。

throw 12345;
throw "Hello world!";
throw true;
throw { name: "JavaScript"};

throw语句用来抛出一个用户自定义的异常。当前函数的执行将被停止(throw之后的语句将不会执行),并且控制将被传递到调用堆栈中的第一个catch块(此时catch块中的error对象就是throw语句所抛出的)。如果调用者函数中没有catch块,程序将会终止。

通过使用某种内置错误类型,可以更真实地模拟浏览器错误。每种错误类型的构造函数接收一个参数,即实际的错误消息。浏览器会像处理自己生成的错误一样,来处理抛出的错误。换句话说,浏览器会以常规方式报告这一错误,并且会显示这里的自定义错误消息。

throw new Error("Something bad happened.");
throw new SyntaxError("I don’t like your syntax.");
throw new TypeError("What type of variable do you take me for?");

利用原型链还可以通过继承Error来创建自定义错误类型。

function CustomError(message){
    this.name = "CustomError";
    this.message = message;
}
CustomError.prototype = new Error();
throw new CustomError("My message");

浏览器对待继承自Error的自定义错误类型,就像对待其他错误类型一样。如果要捕获自己抛出的错误并且把它与浏览器错误区别对待的话,创建自定义错误是很有用的。

抛出错误的时机

要针对函数为什么会执行失败给出更多信息,抛出自定义错误是一种很方便的方式。应该在出现某种特定的已知错误条件,导致函数无法正常执行时抛出错误。

function process(values){
    if (!(values instanceof Array)){
        throw new Error("process(): Argument must be an array.");
    }
    values.sort();
    for (var i=0, len=values.length; i < len; i++){
        if (values[i] > 100){
            return values[i];
        }
    }
    return -1;
}

如果values参数不是数组,就会抛出一个错误。错误消息中包含了函数的名称,以及为什么会发生错误的明确描述。这样就方便了错误的定位,在开发过程中应重点关注函数和可能导致函数执行失败的因素。良好的错误处理机制应该可以确保代码中只发生你自己抛出的错误。

说到抛出错误与捕获错误,我们认为只应该捕获那些你确切地知道该如何处理的错误。捕获错误的目的在于避免浏览器以默认方式处理它们;而抛出错误的目的在于提供错误发生具体原因的消息。

三、错误事件

任何没有通过try-catch处理的错误都会触发window对象的error事件,在任何Web浏览器中,onerror事件处理程序都不会创建event对象,但它可以接收三个参数:错误消息、错误所在的URL和行号。要指定onerror事件处理程序,必须使用如下所示的DOM0级技术,它没有遵循“DOM2 级事件”的标准格式。

只要发生错误,无论是不是浏览器生成的,都会触发error 事件,并执行这个事件处理程序。然后,浏览器默认的机制发挥作用,像往常一样显示出错误消息。像下面这样在事件处理程序中返回false可以阻止浏览器报告错误的默认行为。

window.onerror = function(message, url, line){
    alert(message);
    return false;
};

通过返回false,这个函数实际上就充当了整个文档中的try-catch语句,可以捕获所有无代码处理的运行时错误。这个事件处理程序是避免浏览器报告错误的最后一道防线,理想情况下,只要可能就不应该使用它。只要能够适当地使用try-catch语句,就不会有错误交给浏览器,也就不会触发error事件。

图像也支持error事件。只要图像的src特性中的URL不能返回可以被识别的图像格式,就会触发error事件。此时的error事件遵循DOM格式,会返回一个以图像为目标的event 对象。

var image = new Image();
EventUtil.addHandler(image, "load", function(event){
    alert("Image loaded!");
});
EventUtil.addHandler(image, "error", function(event){
    alert("Image not loaded!");
});
image.src = "smilex.gif"; //指定不存在的文件

发生error事件时,图像下载过程已经结束,也就是说不能再重新下载了。

四、常见的错误类型

  • 类型转换错误

使用了相等和全等操作符比较了数值5 和字符串"5"。相等操作符首先会将数值5转换成字符串"5",然后再将其与另一个字符串"5"进行比较,结果是true。全等操作符知道要比较的是两种不同的数据类型,因而直接返回false。使用全等和非全等操作符,可以避免发生因为使用相等和不相等操作符引发的类型转换错误。

在流控制语句中使用非布尔值,是极为常见的一个错误来源。为避免此类错误,就要做到在条件比较时切实传入布尔值。实际上,执行某种形式的比较就可以达到这个目的。

function concat(str1, str2, str3){
    var result = str1 + str2;
    if (str3){
    //绝对不要这样!!! 因为不是只有字符串值才可以转换为true
    //并不是只有undefined 才会被转换成false
        result += str3;
    }
    // 应使用如下比较
    if (typeof str3 == "string"){ //恰当的比较
        result += str3;
    }
    return result;
}
  • 数据类型错误

在使用变量和函数参数之前,不会对它们进行比较以确保它们的数据类型正确。为了保证不会发生数据类型错误,只能编写适当的数据类型检测代码。

//安全,非数组值将被忽略
function reverseSort(values){
    if (values instanceof Array){ 
        values.sort();
        values.reverse();
    }
}
// 不要使用下列判断检测
// if (values){}  任何非数组且能转换为true的值都将导致错误
// if (values != null){}   不等于null 只能屏蔽null 和 undefined
// if (typeof values.sort == "function"){}  只针对某个特性检测 可以定义一个拥有同名sort方法的对象
  • 通信错误

JavaScript与服务器之间的任何一次通信,都有可能会产生错误。

第一种通信错误与格式不正确的URL或发送的数据有关。最常见的问题是在将数据发送给服务器之前,没有使用encodeURIComponent()对查询字符串或数据进行编码。另外,在服务器响应的数据不正确时,也会发生通信错误。

五、区分致命错误和非致命错误

区分非致命错误和致命错误的主要依据,就是看它们对用户的影响。设计良好的代码,可以做到应用程序某一部分发生错误不会不必要地影响另一个实际上毫不相干的部分。

对于非致命错误,可以根据下列一或多个条件来确定,没有必要因为发生了非致命错误而打断用户:

  • 不影响用户的主要任务;
  • 只影响页面的一部分;
  • 可以恢复;
  • 重复相同操作可以消除错误。

致命错误,可以通过以下一或多个条件来确定:

  • 应用程序根本无法继续运行;
  • 错误明显影响到了用户的主要操作;
  • 会导致其他连带错误。

在发生致命错误时,应该立即给用户发送一条消息,告诉他们无法再继续手头的事情了。假如必须刷新页面才能让应用程序正常运行,就必须通知用户,同时给用户提供一个点击即可刷新页面的按钮。

记录错误

首先需要在服务器上创建一个页面(或者一个服务器入口点),用于处理错误数据。这个页面的作用无非就是从查询字符串中取得数据,然后再将数据写入错误日志中集中保存错误日志,以便查找重要错误的原因。

function logError(sev, msg){
    var img = new Image();
    img.src = "log.php?sev=" + encodeURIComponent(sev) + "&msg=" +encodeURIComponent(msg);
}

for (var i=0, len=mods.length; i < len; i++){
    try {
        mods[i].init();
    } catch (ex){
        logError("nonfatal", "Module init failed: " + ex.message);
    }
}

只要是使用try-catch语句,就应该把相应错误记录到日志中。记录到服务器中的错误消息应该尽可能多地带有上下文信息,以便鉴别导致错误的真正原因。

使用了Image对象来发送请求,这样做非常灵活,主要表现如下几方面。

  • 所有浏览器都支持Image对象,包括那些不支持XMLHttpRequest对象的浏览器。
  • 可以避免跨域限制。通常都是一台服务器要负责处理多台服务器的错误,而这种情况下使用XMLHttpRequest是不行的。
  • 在记录错误的过程中出问题的概率比较低。大多数Ajax通信都是由JavaScript库提供的包装函数来处理的,如果库代码本身有问题,而你还在依赖该库记录错误,可想而知,错误消息是不可能得到记录的。
六、在控制台输出消息

可以通过console对象向JavaScript控制台中写入消息,Opera 10.5之前的版本中,JavaScript控制台可以通过opera.postError()方法来访问。这个方法接受一个参数,即要写入到控制台中的参数,别看opera.postError()方法的名字好像是只能输出错误,但实际上能通过它向JavaScript 控制台中写入任何信息。

还有一种方案是使用LiveConnect,也就是在JavaScript中运行Java代码。FirefoxSafariOpera都支持LiveConnect,因此可以操作Java控制台。例如,通过下列代码就可以在JavaScript中把消息写入到Java控制台。

java.lang.System.out.println("Your message");

这个log()函数检测了哪个JavaScript控制台接口可用,然后使用相应的接口。可以在任何浏览器中安全地使用这个函数,不会导致任何错误

function log(message){
    if (typeof console == "object"){
        console.log(message);
    } else if (typeof opera == "object"){
        opera.postError(message);
    } else if (typeof java == "object" && typeof java.lang == "object"){
        java.lang.System.out.println(message);
    }
}

七、常见的IE错误

  • 操作终止 operation aborted

在修改尚未加载完成的页面时,就会发生操作终止错误。发生错误时,会出现一个模态对话框,告诉你“操作终止”,单击确定(OK)按钮,则卸载整个页面,继而显示一张空白屏幕;此时要进行调试非常困难。当<script>节点被包含在某个元素中,而且JavaScript代码又要使用appendChild()innerHTML或其他DOM方法修改该元素的父元素或祖先元素时,将会发生操作终止错误(因为只能修改已经加载完毕的元素)。

  • 无效字符 illegal character

JavaScript文件必须只包含特定的字符。在JavaScript文件中存在无效字符时,IE会抛出无效字符(invalid character)错误。

  • 未找到成员 Member not found

如果在对象被销毁之后,又给该对象赋值,就会导致未找到成员错误。

发生这个错误的最常见情形是使用event对象的时候。IE中的event 对象是window的属性,该对象在事件发生时创建,在最后一个事件处理程序执行完毕后销毁。一般在事件处理程序中将event对象赋值给一个变量var e = window.event,事件处理程序执行完毕后,该变量就会自动被销毁,如果在事件处理程序中,有延迟函数去访问或修改该变量的属性,就会导致未找到成员错误。

  • 系统无法找到指定资源 The system cannot locate the resource specified

在使用JavaScript请求某个资源URL,而该URL的长度超过了IEURL最长不能超过2083个字符的限制时,就会发生这个错误。同时也限制用户在浏览器自身中使用的URL 长度(其他浏览器对URL的限制没有这么严格)。IEURL路径还有一个不能超过2048个字符的限制。

©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 205,132评论 6 478
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 87,802评论 2 381
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 151,566评论 0 338
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,858评论 1 277
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,867评论 5 368
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,695评论 1 282
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 38,064评论 3 399
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,705评论 0 258
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 42,915评论 1 300
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,677评论 2 323
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,796评论 1 333
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,432评论 4 322
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 39,041评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,992评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,223评论 1 260
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 45,185评论 2 352
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,535评论 2 343

推荐阅读更多精彩内容

  •   由于 JavaScript 本身是动态语言,而且多年来一直没有固定的开发工具,因此人们普遍认为它是一种最难于调...
    霜天晓阅读 759评论 0 1
  • 0. 写在前面 当你开始工作时,你不是在给你自己写代码,而是为后来人写代码。 —— Nichloas C. Zak...
    康斌阅读 5,297评论 1 42
  • 概要 64学时 3.5学分 章节安排 电子商务网站概况 HTML5+CSS3 JavaScript Node 电子...
    阿啊阿吖丁阅读 9,095评论 0 3
  • 心如流水云烟仄, 漫步乡村放酒歌。 惊鹊绕枝风叶坠, 闲中得趣任蹉跎。
    宫岳霖阅读 485评论 1 12
  • 【每日清单】520/700次记录,2018-4-16,降温+小雨 【三件事】 1. []第一要务:满减满赠设计构思...
    伽蓝214阅读 151评论 0 0