try..finally
你可能很熟悉try..catch
块儿是如何工作的。但是你有没有停下来考虑过可以与之成对出现的finally
子句呢?事实上,你有没有意识到try
只要求catch
和finally
两者之一,虽然如果有需要它们可以同时出现。
在finally
子句中的代码 总是 运行的(无论发生什么),而且它总是在try
(和catch
,如果存在的话)完成后立即运行,在其他任何代码之前。从一种意义上说,你似乎可以认为finally
子句中的代码是一个回调函数,无论块儿中的其他代码如何动作,它总是被调用。
那么如果在try
子句内部有一个return
语句将会怎样?很明显它将返回一个值,对吧?但是调用端代码是在finally
之前还是之后才收到这个值呢?
function foo() {
try {
return 42;
}
finally {
console.log( "Hello" );
}
console.log( "never runs" );
}
console.log( foo() );
// Hello
// 42
return 42
立即运行,它设置好foo()
调用的完成值。这个动作完成了try
子句而finally
子句接下来立即运行。只有这之后foo()
函数才算完成,所以被返回的完成值交给console.log(..)
语句使用。
对于try
内部的throw
来说,行为是完全相同的:
function foo() {
try {
throw 42;
}
finally {
console.log( "Hello" );
}
console.log( "never runs" );
}
console.log( foo() );
// Hello
// Uncaught Exception: 42
现在,如果一个异常从finally
子句中被抛出(偶然地或有意地),它将会作为这个函数的主要完成值进行覆盖。如果try
块儿中的前一个return
已经设置好了这个函数的完成值,那么这个值就会被抛弃。
function foo() {
try {
return 42;
}
finally {
throw "Oops!";
}
console.log( "never runs" );
}
console.log( foo() );
// Uncaught Exception: Oops!
其他的诸如continue
和break
这样的非线性控制语句表现出与return
和throw
相似的行为是没什么令人吃惊的:
for (var i=0; i<10; i++) {
try {
continue;
}
finally {
console.log( i );
}
}
// 0 1 2 3 4 5 6 7 8 9
console.log(i)
语句在continue
语句引起的每次循环迭代的末尾运行。然而,它依然是运行在更新语句i++
之前的,这就是为什么打印出的值是0..9
而非1..10
。
注意: ES6在generator(参见本系列的 异步与性能)中增加了yield
语句,generator从某些方面可以看作是中间的return
语句。然而,和return
不同的是,一个yield
在generator被推进前不会完成,这意味着try { .. yield .. }
还没有完成。所以附着在其上的finally
子句将不会像它和return
一起时那样,在yield
之后立即运行。
一个在finally
内部的return
有着覆盖前一个try
或catch
子句中的return
的特殊能力,但是仅在return
被明确调用的情况下:
function foo() {
try {
return 42;
}
finally {
// 这里没有 `return ..`,所以返回值不会被覆盖
}
}
function bar() {
try {
return 42;
}
finally {
// 覆盖前面的 `return 42`
return;
}
}
function baz() {
try {
return 42;
}
finally {
// 覆盖前面的 `return 42`
return "Hello";
}
}
foo(); // 42
bar(); // undefined
baz(); // "Hello"
一般来说,在函数中省略return
和return;
或者return undefined;
是相同的,但是在一个finally
块儿内部,return
的省略不是用一个return undefined
覆盖;它只是让前一个return
继续生效。
事实上,如果将打了标签的break
(在本章早先讨论过)与finally
相组合,我们真的可以制造一种疯狂:
function foo() {
bar: {
try {
return 42;
}
finally {
// 跳出标记为`bar`的块儿
break bar;
}
}
console.log( "Crazy" );
return "Hello";
}
console.log( foo() );
// Crazy
// Hello
但是……别这么做。说真的。使用一个finally
+ 打了标签的break
实质上取消了return
,这是你在尽最大的努力制造最令人困惑的代码。我打赌没有任何注释可以拯救这段代码。