错误处理

本文共--字 阅读约--分钟 | 浏览: -- Last Updated: 2020-04-21

一、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语句相配的还有一个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事件时,图像下载过程已经结束,也就是说不能再重新下载了。

四、常见的错误类型

类型转换错误

全等操作符知道要比较的是两种不同的数据类型,使用全等和非全等操作符,可以避免发生因为使用相等和不相等操作符引发的类型转换错误。

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

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);
  }
}