捕获并处理错误

在具体介绍如何捕获并处理错误前,先制造错误,看看错误出现后代码将如何执行。

以下代码首先声明了一个变量 a,它是任意类型,值为 null,接着将变量 a 的 name 属性值赋给变量 b,在赋给变量 b 的语句前后分别输出 “开始赋值” 和 “结束赋值”。

let a: any = null;
console.log("开始赋值")
let b: any = a.name;
console.log("结束赋值");

如果程序正常执行,它将会输出 “开始执行” 和 “结束执行”,但由于变量 a 为 null,它不具有 name 属性,因此为变量 b 赋值的语句将产生错误,导致程序中断,不会输出 “结束赋值”。代码执行结果如下。

> 开始赋值
> Uncaught TypeError: Cannot read property 'name' of null

可以看出以上代码的健壮性不高,不具备从错误中恢复的能力,一旦某处产生错误,后续所有的代码将无法执行。

在 TypeScript 中,使用 try/catch 语句捕获并处理错误。使用 try 语句创建一个需要检测错误的代码块,使用 catch 语句创建一个出现错误时需要执行的代码块。具体语法如下。

try {
     //需要检测错误的代码块
}
catch {
     //出现错误时执行的代码块
}

一旦 try 块产生错误,只中断 try 块的执行,然后跳到 catch 块,继续执行。注意,catch 中的语句只在 try 中的语句出现错误时才执行,如果 try 块中的语句正常执行,那么 catch 块中的语句不会执行。

下面将前面的示例以 try/catch 语句的形式改写,代码如下。

let a: any = null;
try {
    console.log("开始赋值");
    let b: any = a.name;
}
catch {
    console.log("赋值错误");
}
console.log("结束赋值");

之后再执行代码,为变量 b 赋值的语句依然会产生错误,此时会跳至 catch 块中,继续执行,try/catch 语句结束后,将继续执行其他后续代码。执行结果如下。

> 开始赋值
> 赋值错误
> 结束赋值

注意,catch 块中也可能产生错误,继而中断后续代码的执行。例如,在以下代码中,把 catch 块中变量 a 的 name 属性赋给变量 c,但变量 a 不具有 name 属性,因此 catch 块之后的代码都不会执行。

let a: any = null;
try {
    console.log("开始赋值");
    let b: any = a.name;
}
catch {
    let c: any = a.name;
    console.log("赋值错误");
}
console.log("结束赋值");

输出结果如下。

> 开始赋值
> Uncaught TypeError: Cannot read properties of null (reading 'name')

在实际项目中,可能会有一些文件操作或者数据库连接操作,需要在执行完这些操作后释放文件或数据库连接。在进行文件或数据库操作时,虽然使用 try 语句检测错误,如果 try 语句出现错误,则在 catch 语句中进行处理,但最坏的情况是 catch 语句中也产生了错误,这种情况会中断后续代码的执行,使释放文件或数据库连接的语句无法执行,一直占用资源。在这种情况下,释放文件或数据库连接的语句是无论如何都要执行的。

TypeScript 支持 finally 块,它用于放置无论如何都要执行的代码。当 try/catch 语句执行完毕后,无论是否产生错误,finally 块都会执行。使用 finally 块,就可以解决如释放文件或释放数据库连接等的必要语句无法执行的问题。

try/catch/finally 语句的语法如下。

try {
    //需要检测错误的代码块
}
catch {
    //出现错误时执行的代码块
}
finally {
    //无论try/catch块结果如何都要执行的代码块
}

接下来,修改前面的示例,增加 finally 块,代码如下。

let a: any = null;
try {
    console.log("开始赋值");
    let b: any = a.name;
}
catch {
    let c: any = a.name;
    console.log("赋值错误");
}
finally {
    console.log("结束赋值");
}

输出结果如下,可以看到虽然 catch 块中产生了错误,但 finally 块中的语句依然会执行。

> 开始赋值
> 结束赋值
> Uncaught TypeError: Cannot read property 'name' of null

由于 finally 块中的代码一定会执行,因此如果 finally 块中有 return 语句,就会导致 try 和 catch 块中的 return 语句失效,只有 finally 块中的 return 语句生效。例如,以下代码中 test() 函数的 try、catch、finally 语句中都有 return 语句,输出结果后,会发现输出值为 2,说明只有 finally 块中的 return 语句有效。

function test(): number {
    try {
        return 0;
    }
    catch {
        return 1;
    }
    finally {
        return 2;
    }
}

console.log(test());