【javaScript基础】异常处理
理解异常在javaScript面向对象编程是非常重要的,异常是一种非常强大的处理错误的方式。
错误处理
首先我们来看一个有问题的代码:
nonexistant();
在以上这个例子中,访问一个不存在的变量,在这种情况下,程序会怎么处理?很早以前的处理方式就是程序直接崩溃死掉,所以我们不能容忍这种处理方式,需要有办法来处理。
最简单的处理方式是先检查,像这样:
if (window.func) { func(); }
上面这种处理方式,仍然可能还会出现错误。因为window.func可能不是一个函数,因此我们仍需要这样检查:
if (typeof(func) == 'function') { func(); }
在上面的例子中,typeof确保变量存在并确保是个函数。
我们满怀希望地做了很多需要的检查来确保执行func是安全的。但是如果func的函数体内有错误呢?我们更想做的是处理错误,而不是让程序死掉。
我们可以用trycatch结构来处理。
Try catch 结构
用try…catch来代替我们常用的if语句结构来处理错误,我们用如下代码来改写上面的例子:
try { func() } catch(e) { alert(e) }
如果在try块中出现错误,这个时候catch块会起作用,参数e被赋值为一个特别的异常对象,该对象包括异常发生时的一些信息。
变量e是Error对象的一个实例(或者从TypeError,ReferenceError等继承)。
这个错误的属性在不同浏览器有点不一样,详情参考 Error in MDN 和 Error inMSDN。
但是基本属性是相同的:
name:错误类型,对于浏览器产生的error会匹配error构造函数如TypeError, ReferenceError 等。
message: 告诉我们更详细的error信息。
在以下例子中,我们在try块中增加其他声明,name和message会被打印出来。
try { var a = 5 var res = func(a) if (res > 0) doA() else doB() } catch(e) { alert("name:" + e.name + "\nmessage:" + e.message) }
很多异常都可以被try..catch捕获,你仅需通过try来检测可能的错误就行了。
获取栈的信息
Firefox,Chrome, Opera浏览器提供了stack属性,通过stack属性我们可以看到所有导致异常的嵌套的调用信息,例子如下:
function f(a) { g(a+1) } function g(a) { notexists; } try { f(1) } catch(e) { alert(e.stack)
不幸的是在IE中没有这个属性,甚至在IE9中也没有。
Try…catch…finally完整形式
完整的组成形式是由3部分组成:
try { .. try statemenets .. } catch(exception) { .. catch statements .. } finally { .. finally statements ..
执行过程如下:
1. Try中的声明会被执行,如果没有错误发生,catch部分会被忽视。
2. 如果错误发生,exception变量会被赋值为错误对象,并且catch中的声明也会被执行。
3. 在以上两种情况下,在try或者catch中不管有没有执行,finally代码都会被执行。
try…catch…finally…return
在以下例子中,如果try中有return语句并且有finally块时,finally中的执行完后,执行return语句。
function inc(a) { try { return a+1 } catch(e) { // .. } finally { alert('done') } } alert( inc(1) ) //执行结果:‘done’, 2
Throw声明
所有的错误可以被分成两种:
1. 程序设计错误:一般是由开发人员造成的,如输入错误。
2. 异常流错误:这个错误是程序执行过程中的正常部分。一个常见的错误是表单验证。如果用户输入了一些错误,正常的做法是处理这个错误并且叫用户重复输入。
用try…catch来处理异常错误,需要通过throw手动抛出错误。
语法是:throw e,e可以是任何东西。不管你抛出什么,都可以被catch…捕获,但是如果在try块外面抛出程序可能会崩溃。
下面的例子展示了throw是如何工作的。
try { throw 5 } catch(e) { alert("Caught: "+e) }
表单验证例子
例如,我们对年龄进行验证,检查是否合法。
function validateAge(age) { // age is a text to check if (age === '') return; // no age is valid age = +age if (isNaN(age)) { throw { name: 'BadAge', message: 'Invalid age' } } if (age < 5 || age > 150) { throw { name: 'BadAge', message: 'Age out of range' } } } try { var age = prompt("Enter your age please?") validateAge(age) alert("The age is accepted!") } catch(e) { alert("Error: "+e.message) }
经常来说,抛出的异常对象最好是从Error对象继承,提供一种更好的方式来管理error在上面的例子中应该这样实现:throw new BadAgeError("Invalid age")。
验证变化
现在如果需要添加一个验证条件,验证用户所提供的值是必须提供的,并且是有效的年龄。比如我们实现了validateAge和validateRequired。
错误的检测方法
var value = input.value // VALIDATE var error = validateRequired(value) if (!error) { error = validateAge(value) } if (!error) { /* another validator... */ } // FINISHED VALIDATING if (error) { /* process error */ } else { /* success */ }
Try…catch方法
这种方式就是当检测到错误时手动抛出。
var value = input.value try { validateRequired(value) validateAge(value) // other validators in a row /* success */ } catch(e) { /* process error */ }
我们不需要一个一个地来检测,只需要把可能出现错误的验证放到try块中就行了,如果一有错误,在catch中就会捕获到,没有错误自然很顺利地执行,不会进入到catch块中。
比较
用try..catch处理错误有一些优点和缺点:
1. Try…catch方法处理错误更干净、简单可依赖,能够捕获所有错误。
2. 有可能存在一些不能检测的异常,try…catch是唯一能解决的方法。例如检测浏览器的一些特性。
3. Try…catch结构会占据几行代码的位置,看起来代码不太优雅。
异常分析和重新抛出
有时代码会产生不同的错误,这种情况下,经常用if来选择正确的处理方式。以下是伪代码。
try { // 1. do smth } catch(e) { if (e instanceof ValidationError) { // 2.1 process e } else if (e instanceof PermissionError) { // 2.2 process e } else { // 3. we don't know how to deal with e throw e } }
1. 在try块中的代码比较复杂,也许会抛出异常,有些异常我们知道怎么处理,比如ValidationError,但是其他一些异常不知道。
2. 在catch块中,我们分析异常并且处理它。
3. 否则,异常重新抛出,假定在外面还有一层try…catch块知道怎么处理该异常。
异常要么被重新抛出,要么被处理,千万不要不管它,除非你完全知道你在做什么。
try { func() } catch(e) { if (e instanceof KnownError) { // ... } }
在上面代码片段中,除了KnownError异常外,其他异常都被忽视了。
坦白地说,在java的世界里有这种不处理异常的情况存在,但是留下不处理的异常总有隐患。
想象下,如果在func中代码有输入错误,这将会很难调试,因为TypeError 和ReferenceError 异常被忽视了。
总结
1. Try…catch…finally结构允许你在try块中加入几种声明,可以在各自的catch块中进行异常处理。
2. 允许处理所有错误,包括JS自己产生的和手动抛出的。
3. 严格来说javaScript允许throw任何值,但是推荐所有的错误继承Error对象,形成继承层级。在这种情况下,instanceof 工作得很好。例如你可以捕获所有e instanceof ValidationError的异常,ValidationError包括AgeValidationError, RequiredValidationError 等。