TypeScript:异常处理(try, catch, finally,throw,Error) 作者:马育民 • 2025-10-09 10:59 • 阅读:10004 # 提出问题 假设由用户输入 `s` 的值,然后执行解析为 JSON,当用户赋值 `s` 不符合 JSON 格式时,就会抛出异常,**中断程序运行**,如下: ``` console.log("执行开始") let s:string = "{ abc }" // 用户赋值 `s` 不符合 JSON 格式 JSON.parse(s); // 因为解析错误,抛出异常,程序不再继续运行 let res = 1 + 3 // 没有执行该行代码 console.log("执行结束,res:",res) // 没有执行该行代码 ``` 这是非常严重的错误:不能因为一个错误,导致程序不再继续往下运行,比如windows操作系统,不能因为一个错误就蓝屏死机 ### 解决 使用异常处理:捕获异常 (try...catch) # 介绍 TypeScript 继承 JavaScript 的 `try...catch...finally` 异常处理模型。当程序执行过程中发生错误时,会创建一个 **异常对象** 并将其 **抛出**。 开发者可以使用 `try...catch` 结构来捕获这个异常,防止程序崩溃。 ### 概念:throw, try, catch, finally * **`throw`**: 用于**主动抛出**一个异常。 * **`try`**: 包裹可能**抛出异常**的代码块。 * **`catch`**: 定义一个块来**捕获并处理**从 `try` 块中抛出的异常。 * **`finally`**: 定义一个块,无论是否发生异常,都会**最终执行**,通常用于资源清理。 # 捕获异常 (try...catch) 使用 `try...catch` 来安全地执行可能出错的代码 ```typescript console.log("执行开始") let s:string = "{ abc }" try { // 可能抛出异常的代码 JSON.parse(s); // 解析错误格式的JSON } catch (error) { // 处理捕获到的异常 console.error("捕获异常:", error); } let res = 1 + 3 // 仍然执行该行代码 console.log("执行结束,res:",res) // 仍然执行该行代码 ``` # Error 异常类 所有内置异常(如 TypeError、RangeError)和自定义异常都继承自 `Error` 类 ### 包含属性 - **message**:错误描述信息(字符串),通过 `Error` 构造函数传入 - **name**:(字符串)错误的类型名称,用于区分不同异常类型。 - 内置类型的 name 固定:如 `TypeError` 的 name 是 `"TypeError"`。 - 自定义异常需 **手动设置**(否则默认继承 `Error` 的 `name`,即:`"Error"` )。 - stack:错误堆栈跟踪(包含错误发生的位置和调用链) ### 例子 ```typescript console.log("执行开始") let s:string = "{ abc }" try { // 可能抛出异常的代码 JSON.parse(s); // 解析错误格式的JSON } catch (error) { // 处理捕获到的异常 console.error("捕获异常,name:", error.name); console.error("捕获异常,message:", error.message); console.error("捕获异常,stack:", error.stack); } let res = 1 + 3 // 仍然执行该行代码 console.log("执行结束,res:",res) // 仍然执行该行代码 ``` # 错误类型与类型安全 TypeScript 4.4+ 中,`catch` 块的错误变量默认被推断为 `unknown` 类型(更严格的类型检查):必须先通过类型守卫(如 `instanceof`、`typeof`)缩小类型范围,才能安全访问其属性。 ### 处理 unknown 类型 ```typescript console.log("执行开始") let s:string = "{ abc }" // let s:string = '{ "name":"李雷" }' try { // 可能抛出异常的代码 JSON.parse(s); // 解析错误格式的JSON } catch (error) { // 'error' 是 'unknown',不能直接访问属性 // console.log(error.message); // ❌ 编译错误! // 必须进行类型检查 if (typeof error === 'string') { console.log('错误字符串:', error); } else if (error instanceof Error) { console.log('错误消息:', error.message); console.log('堆栈跟踪:', error.stack); // 在开发环境中很有用 } else { console.log('未知错误类型:', error); } } let res = 1 + 3 // 仍然执行该行代码 console.log("执行结束,res:",res) // 仍然执行该行代码 ``` # finally 代码块 有时,一些特定的代码 **无论异常是否发生**,**都需要执行**。另外,因为异常会引发** 程序跳转**,导致有些语句 **执行不到**。 finally 就是解决这个问题的,在```finally代码块```中的代码 **一定会执行**。 ### 应用场景 当我们在try语句块中打开了一些物理资源(磁盘文件/网络连接/数据库连接等),我们都得在 **使用完之后**,及时 **关闭**。 ### 语法 ``` try{ }catch(){ }finally{ } ``` 或者 ``` try{ }finally{ } ``` **注意:** finally代码块不能单独使用。 **例子:** ```java console.log("执行开始") let s:string = "{ abc }" // let s:string = '{ "name":"李雷" }' try { // 可能抛出异常的代码 JSON.parse(s); // 解析错误格式的JSON } catch (error) { // 处理捕获到的异常 console.error("捕获异常:", error); } finally { console.log("一定会执行finally代码块") } let res = 1 + 3 // 仍然执行该行代码 console.log("执行结束,res:",res) // 仍然执行该行代码 ``` # 抛出异常 (throw) ### 为什么要抛出异常 定义 `login()` 登录函数如下,调用该函数登录时,如果登录成功就返回 `True`,如果 **用户名或密码错误** 就返回 `False` 这种设计是不好的,没有错误信息,即:**不知道用户名不存在,还是密码错误** ``` function login(username:string,password:string) : boolean{ let ret:boolean = false if(username == 'lilei' ){ if(password == '123456'){ ret = true }else{ ret = false } }else{ ret = false } return ret } ``` 调用 `login()` 函数进行登录: ``` let res : boolean = login("lilei", "123") if (res){ console.log("登录成功") }else { console.log("登录失败") } ``` ### 解决 定义 `login()` 函数,登录失败时,使用 `throw` 语句抛出异常,将 **登录错误信息封装到异常对象中** ``` function login(username:string,password:string) : boolean{ let ret:boolean = false if(username == 'lilei' ){ if(password == '123456'){ ret = true }else{ throw Error("密码错误!") } }else{ throw Error("用户名不存在!") } return ret } ``` 调用该函数,并且用 `try...catch` 捕获异常,如下: ``` try { // login("lilei", "123") // 提示密码错误 login("hanmeimei", "123456") // 提示用户名不存在 // login("lilei", "123456") // 登录成功 }catch (error){ console.log("登录失败:",error.message) } console.log("登录成功") ``` ### 注意 虽然可以抛出任何值,但 **强烈推荐抛出 `Error` 或其子类的实例**。 ```typescript // ❌ 不推荐:抛出原始值(缺乏上下文) throw "网络连接失败"; // ✅ 推荐:抛出 Error 对象(包含消息和堆栈跟踪) throw new Error("网络连接失败"); // 你也可以抛出其他类型的对象,但 Error 是标准做法 throw { code: 404, message: "页面未找到" }; ``` # 创建自定义错误类 为了更好地组织和分类错误,建议创建自定义错误类,然后抛出该自定义类。 ### 例子:登录错误 定义登录失败异常: ``` class LoginError extends Error { constructor(message: string) { super(message); this.name = "LoginError" // 否则继承 Error,即:值为 "Error" } } ``` 定义验证密码不符合要求异常: ``` class ValidPasswordError extends Error { constructor(message: string) { super(message); this.name = "ValidPasswordError" // 否则继承 Error,即:值为 "Error" } } ``` 定义登录函数: ``` function login(username:string,password:string) : boolean{ let ret:boolean = false // 校验密码长度 if(password.length < 6){ throw new ValidPasswordError("密码长度不足6位,请重新输入!") } if(username == 'lilei' ){ if(password == '123456'){ ret = true }else{ throw new LoginError("密码错误!") } }else{ throw new LoginError("用户名不存在!") } return ret } ``` 执行登录: ``` try { login("lilei", "123") // 提示:密码不符合要求: 密码长度不足6位,请重新输入! // login("hanmeimei", "123456") // 提示:登录失败: 用户名不存在! // login("lilei", "123456") console.log("登录成功") }catch (error){ // error 是 unknow 类型,必须进行类型检查 if(error instanceof LoginError) { console.log("登录失败:", error.message) }else if(error instanceof ValidPasswordError){ console.log("密码不符合要求:",error.message) } } ``` # 使用建议 ### 始终抛出 `Error` 实例 避免抛出原始值。使用 `Error` 或其子类提供更好的上下文和堆栈跟踪。 ### 提供有意义的错误信息 错误消息应该清晰、具体,帮助开发者或用户理解问题所在。 ### 使用自定义错误类进行分类 通过继承 `Error` 创建特定领域的错误类(如 `ValidationError`, `ApiError`, `DatabaseError`),便于识别和处理不同类型的错误。 ### 在 `catch` 块中进行类型检查 利用 TypeScript 的 `unknown` 类型和类型守卫(`instanceof`, `typeof`)安全地访问错误属性。 ### 不要忽略异常 捕获异常后,至少要记录它。避免空的 `catch` 块。 ```typescript // ❌ 不好 try { doSomething(); } catch (error) { // 什么也不做 - 错误被吞噬了 } // ✅ 好 try { doSomething(); } catch (error) { console.error("Something went wrong:", error); // 或者重新抛出,或返回默认值等 } ``` # 总结 TypeScript 的异常处理结合了 JavaScript 的运行时机制和 TypeScript 的静态类型检查优势。通过: 1. **理解基础**:掌握 `throw`, `try...catch`, `finally`。 2. **利用类型系统**:正确处理 `catch` 块中的 `unknown` 类型。 3. **创建自定义错误**:使用继承 `Error` 的类来表示特定错误。 4. **妥善处理异步错误**:在 `async/await` 中使用 `try...catch`。 5. **遵循最佳实践**:提供有意义的信息,不忽略错误,适当记录。 原文出处:http://malaoshi.top/show_1GW20buJ8Xcn.html