本章介绍 JavaScript 的异常处理机制。首先,我们将概述什么是异常处理。
在异常处理中,您通常会将紧密耦合的语句分组。 如果在执行这些语句时,其中一个语句导致错误,则继续执行其余语句毫无意义。相反,您应该尝试尽可能优雅地从错误中恢复。这有点类似于事务(但不具有原子性)。
让我们看看没有异常处理的代码
function
processFiles
()
{
var
fileNames
=
collectFileNames
();
var
entries
=
extractAllEntries
(
fileNames
);
processEntries
(
entries
);
}
function
extractAllEntries
(
fileNames
)
{
var
allEntries
=
new
Entries
();
fileNames
.
forEach
(
function
(
fileName
)
{
var
entry
=
extractOneEntry
(
fileName
);
allEntries
.
add
(
entry
);
// (1)
});
}
function
extractOneEntry
(
fileName
)
{
var
file
=
openFile
(
fileName
);
// (2)
...
}
...
在 (2) 处,对 openFile()
中的错误做出反应的最佳方法是什么?显然,不应再执行语句 (1)。但我们也不想中止 extractAllEntries()
。相反,跳过当前文件并继续处理下一个文件就足够了。为此,我们将异常处理添加到前面的代码中
function
extractAllEntries
(
fileNames
)
{
var
allEntries
=
new
Entries
();
fileNames
.
forEach
(
function
(
fileName
)
{
try
{
var
entry
=
extractOneEntry
(
fileName
);
allEntries
.
add
(
entry
);
}
catch
(
exception
)
{
// (2)
errorLog
.
log
(
'Error in '
+
fileName
,
exception
);
}
});
}
function
extractOneEntry
(
fileName
)
{
var
file
=
openFile
(
fileName
);
...
}
function
openFile
(
fileName
)
{
if
(
!
exists
(
fileName
))
{
throw
new
Error
(
'Could not find file '
+
fileName
);
// (1)
}
...
}
异常处理有两个方面
在 (1) 处,以下结构处于活动状态
processFile() extractAllEntries(...) fileNames.forEach(...) function (fileName) { ... } try { ... } catch (exception) { ... } extractOneEntry(...) openFile(...)
(1) 处的 throw
语句会向上遍历该树,并离开所有结构,直到遇到活动的 try
语句。然后,它会调用该语句的 catch
块,并将异常值传递给它。
JavaScript 中的异常处理机制与大多数编程语言类似:try
语句将语句分组,并允许您拦截这些语句中的异常。
throw
的语法如下:
throw
«
value
»
;
可以抛出任何 JavaScript 值。为简单起见,许多 JavaScript 程序只抛出字符串
// Don't do this
if
(
somethingBadHappened
)
{
throw
'Something bad happened'
;
}
不要这样做。JavaScript 具有用于异常对象的特殊构造函数(请参阅错误构造函数)。使用这些构造函数或对其进行子类化(请参阅第 28 章)。它们的优点是 JavaScript 会自动添加堆栈跟踪(在大多数引擎上),并且它们有空间容纳其他特定于上下文的属性。最简单的解决方案是使用内置构造函数 Error()
if
(
somethingBadHappened
)
{
throw
new
Error
(
'Something bad happened'
);
}
try-catch-finally
的语法如下所示。try
是必需的,并且 catch
和 finally
中至少有一个也必须存在:
try
{
«
try_statements
»
}
⟦
catch
(
«
exceptionVar
»
)
{
«
catch_statements
»
}
⟧
⟦
finally
{
«
finally_statements
»
}
⟧
其工作原理如下
catch
捕获在 try_statements
中抛出的任何异常,无论是直接抛出的还是在其调用的函数中抛出的。提示:如果要区分不同类型的异常,可以使用 constructor
属性来切换异常的构造函数(请参阅constructor 属性的用例)。
finally
始终会被执行,无论 try_statements
(或其调用的函数)中发生什么情况。将其用于应始终执行的清理操作,无论 try_statements
中发生什么情况:
var
resource
=
allocateResource
();
try
{
...
}
finally
{
resource
.
deallocate
();
}
如果其中一个 try_statements
是 return
,则会在之后执行 finally
块(在离开函数或方法之前立即执行;请参阅以下示例)。
可以抛出任何值:
function
throwIt
(
exception
)
{
try
{
throw
exception
;
}
catch
(
e
)
{
console
.
log
(
'Caught: '
+
e
);
}
}
以下是交互过程
> throwIt(3); Caught: 3 > throwIt('hello'); Caught: hello > throwIt(new Error('An error happened')); Caught: Error: An error happened
finally
始终会被执行
function
throwsError
()
{
throw
new
Error
(
'Sorry...'
);
}
function
cleansUp
()
{
try
{
throwsError
();
}
finally
{
console
.
log
(
'Performing clean-up'
);
}
}
以下是交互过程
> cleansUp(); Performing clean-up Error: Sorry...
finally
在 return
语句之后执行:
function
idLog
(
x
)
{
try
{
console
.
log
(
x
);
return
'result'
;
}
finally
{
console
.
log
(
"FINALLY"
);
}
}
以下是交互过程
> idLog('arg') arg FINALLY 'result'
返回值在执行 finally
之前排队
var
count
=
0
;
function
countUp
()
{
try
{
return
count
;
}
finally
{
count
++
;
// (1)
}
}
在执行语句 (1) 时,count
的值已经排队等待返回
> countUp() 0 > count 1
ECMAScript 对以下错误构造函数进行了标准化。描述摘自 ECMAScript 5 规范:
Error
是错误的通用构造函数。此处提到的所有其他错误构造函数都是子构造函数。EvalError
“当前未在本规范中使用。此对象保留是为了与本规范的先前版本兼容。”
RangeError
“表示数值已超出允许范围。”例如:
> new Array(-1) RangeError: Invalid array length
ReferenceError
“表示已检测到无效的引用值。”通常,这是一个未知变量。例如:
> unknownVariable ReferenceError: unknownVariable is not defined
SyntaxError
“表示在解析普通代码或解析 eval()
的参数时发生了解析错误。”例如:
> 3..1 SyntaxError: Unexpected number '.1'. Parse error. > eval('5 +') SyntaxError: Unexpected end of script
TypeError
“表示操作数的实际类型与其预期类型不同。”例如:
> undefined.foo TypeError: Cannot read property 'foo' of undefined
URIError
“表示使用全局 URI 处理函数的方式与其定义不兼容。”例如:
> decodeURI('%2') URIError: URI malformed
以下是错误的属性:
message
name
stack
错误的常见来源是外部错误(输入错误、文件丢失等)或内部错误(程序中的错误)。特别是在后一种情况下,您会遇到意外的异常,并且需要进行调试。通常,您没有运行调试器。对于“手动”调试,有两条信息很有用:
您可以将第一项(数据)的一些内容放入异常对象的 message 或属性中。第二项(执行)在许多 JavaScript 引擎上都通过堆栈跟踪得到支持,堆栈跟踪是创建异常对象时调用堆栈的快照。以下示例打印堆栈跟踪:
function
catchIt
()
{
try
{
throwIt
();
}
catch
(
e
)
{
console
.
log
(
e
.
stack
);
// print stack trace
}
}
function
throwIt
()
{
throw
new
Error
(
''
);
}
以下是交互过程
> catchIt() Error at throwIt (~/examples/throwcatch.js:9:11) at catchIt (~/examples/throwcatch.js:3:9) at repl:1:5
如果您需要堆栈跟踪,则需要使用内置错误构造函数的服务。您可以使用现有的构造函数,并将您自己的数据附加到该构造函数。或者,您可以创建一个子构造函数,其实例可以通过 instanceof
与其他错误构造函数的实例区分开来。唉,这样做(对于内置构造函数)很复杂;请参阅第 28 章,了解如何做到这一点。