提出问题
假设由用户输入 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)
介绍
ArkTS 继承了 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) // 仍然执行该行代码
catch中的异常类型
catch中的异常类型是 any 类型
Error 异常类
所有内置异常(如 TypeError、RangeError)和自定义异常都继承自 Error 类
包含属性
- message:错误描述信息(字符串),通过
Error构造函数传入 - name:(字符串)错误的类型名称,用于区分不同异常类型。
- 内置类型的 name 固定:如
TypeError的 name 是"TypeError"。 - 自定义异常需 手动设置(否则默认继承
Error的name,即:"Error")。
- 内置类型的 name 固定:如
- 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) // 仍然执行该行代码
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){
// 进行类型检查,否则不知道抛的是哪个异常
if(error instanceof LoginError) {
console.log("登录失败:", error.message)
}else if(error instanceof ValidPasswordError){ // 必须进行类型检查
console.log("密码不符合要求:",error.message)
}
}
异常处理建议
向用户反馈
不要让错误只停留在控制台。使用 HarmonyOS 提供的 UI 组件向用户反馈:
prompt.showToast: 显示短暂的提示信息。- 更新
@State变量: 在 UI 组件中显示错误消息(如上例所示)。 - 弹窗 (
AlertDialog): 对于严重错误,使用对话框告知用户。
记录日志
使用 console.log, console.error 记录关键错误,便于调试和线上问题排查。
console.error("[UserService] 获取用户信息失败:", error);
资源清理
在 finally 块中确保关闭文件流、取消定时器、解除事件监听等。
避免吞掉异常
除非有充分理由,否则不要捕获异常后不做任何处理(“吞掉”异常),这会让问题难以发现。
// ❌ 避免这样做
try {
riskyCall();
} catch (error) {
// 什么也不做
}
预防性检查
在某些情况下,可以通过前置检查来避免异常的发生,而不是依赖 try...catch。
// 更好的方式:先检查
if (array && array.length > 0) {
const firstItem = array[0];
}
// 而不是:
// try {
// const firstItem = array[0];
// } catch (error) { ... }
总结
在 ArkTS 中处理异常是构建高质量 HarmonyOS 应用的必备技能。记住以下要点:
- 使用
try...catch: 保护可能出错的代码。 - 抛出
Error实例: 提供清晰的错误信息和堆栈。 - 创建自定义错误: 使错误分类更清晰。
- 处理
unknown: 在catch块中进行类型检查。 - 善用
async/await: 清晰处理异步错误。 - 及时反馈用户: 使用 Toast 或 UI 更新告知用户状态。
- 记录和清理: 记录日志,清理资源。
通过遵循这些原则,您可以显著提升应用的稳定性和用户体验。