TypeScript:异常处理(try, catch, finally,throw,Error)

提出问题

假设由用户输入 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 来安全地执行可能出错的代码

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:错误堆栈跟踪(包含错误发生的位置和调用链)

例子

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 类型

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代码块不能单独使用。

例子:

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 或其子类的实例。

// ❌ 不推荐:抛出原始值(缺乏上下文)
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 块。

// ❌ 不好
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. 遵循最佳实践:提供有意义的信息,不忽略错误,适当记录。

原文出处:/show_1GW20buJ8Xcn.html