你可能不知道的try...finally

你(可能)不知道的系列,经典的you-dont-know-js读书笔记。

综述

finally分支的代码,无论如何都将被执行,并且是在try catch(如果有的话)完成后,在其他代码运行之前,立即执行。

在某种意义上,我们可以将在finally分支中的代码想象为一个回调函数,不管其他部分如何,这个回调函数总是会被执行。

特殊情况

上面综述大家一般都知道,但是在特殊情况下的执行流程呢?

这里指的特殊情况是指,存在改变函数执行流程的语句,比如return throw等。

try分支中有return

如果在try分支中有return语句的话,会怎么样呢?显然最后函数还是要返回一个值的。问题是:fianlly分支中的代码何时执行?

1
2
3
4
5
6
7
8
9
10
11
12
13
14
function foo() {
try {
return 42;
}
finally {
console.log( "Hello" );
}
console.log( "never runs" );
}
console.log( foo() );
// Hello
// 42

首先return 42会被立即的执行,它的作用是设置了调用foo()得到的返回值(注意:并没有结束函数执行)。这个动作结束了try分支,然后finally分支会立即接着执行,finally分支执行完后,foo()函数调用才算完成。所以42会被作为返回值给console.log(...)使用,最后打印出42。

try分支中有throw

对于try中的throw语句,是同样的流程:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
function foo() {
try {
throw 42;
}
finally {
console.log( "Hello" );
}
console.log( "never runs" );
}
console.log( foo() );
// Hello
// Uncaught Exception: 42

finally分支中有throw

现在,如果在finally内部被抛出了一个异常(意外的或者故意的)的话,将会直接结束整个函数,同时如果在try分支中有return xxx语句设置了返回值,这个值将会被抛弃:

1
2
3
4
5
6
7
8
9
10
11
12
13
function foo() {
try {
return 42;
}
finally {
throw "Oops!";
}
console.log( "never runs" );
}
console.log( foo() );
// Uncaught Exception: Oops!

finally分支有return

finally中显示调用return语句的话,可以重写try或者catch分支中的return语句:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
function foo() {
try {
return 42;
}
finally {
// no `return ..` here, so no override
}
}
function bar() {
try {
return 42;
}
finally {
// override previous `return 42`
return;
}
}
function baz() {
try {
return 42;
}
finally {
// override previous `return 42`
return "Hello";
}
}
foo(); // 42
bar(); // undefined
baz(); // "Hello"

正常来说,如果函数中省略了return语句,那么最后的返回值将是undefined,但是在finally块中, 省略return并不等同于return undefined,如果没有return,则仅仅就是让前面的return生效。