Day 14 - ArkTS 错误处理
目标:掌握 throw、try/catch/finally,学会自定义异常类,理解错误处理最佳实践
预计时间:1.5-2小时
前置知识:Day 01-13 所有内容(基础语法、函数、类、继承、接口、泛型、枚举、Type别名)
课前思考
回顾前面学习的函数和类:
// 我们写过很多函数
function divide(a: number, b: number): number {
return a / b;
}
// 也写过很多类
class BankAccount {
private _balance: number = 0;
withdraw(amount: number): void {
this._balance = this._balance - amount;
}
}
思考问题:
- 如果调用
divide(10, 0)会发生什么?程序崩溃还是返回 Infinity? - 如果账户余额不足却调用
withdraw(1000),如何通知调用方"操作失败"? - C++ 用异常处理这类问题,ArkTS 呢?
第一部分:为什么需要错误处理
1.1 程序运行中的意外情况
问题引入:程序不可能永远按预期运行
function readFile(path: string): string {
// 假设这里读取文件
// 问题:文件不存在怎么办?
// 问题:没有权限怎么办?
// 问题:磁盘损坏怎么办?
return "文件内容";
}
function connectToServer(ip: string, port: number): void {
// 问题:网络不通怎么办?
// 问题:服务器拒绝连接怎么办?
// 问题:连接超时怎么办?
}
这些"意外"的共同特点:
- 不是程序逻辑错误(不是 bug)
- 是运行时环境导致的失败
- 需要有一种机制通知调用方"出错了"
1.2 C++ 的异常处理回顾
作为 C++ 开发者,你对这套机制很熟悉:
// C++ 异常处理
#include <stdexcept>
double divide(double a, double b) {
if (b == 0) {
throw std::runtime_error("除数不能为零");
}
return a / b;
}
int main() {
try {
double result = divide(10, 0);
std::cout << "结果:" << result << std::endl;
} catch (const std::runtime_error& e) {
std::cerr << "捕获异常:" << e.what() << std::endl;
}
return 0;
}
C++ 异常处理的核心要素:
throw:抛出异常try:包裹可能抛出异常的代码catch:捕获并处理异常- 异常类:通常继承自
std::exception
1.3 错误码 vs 异常 —— 两种策略对比
策略一:错误码(C 语言风格)
// 返回错误码,通过参数返回结果
function divideWithCode(a: number, b: number, result: number[]): number {
if (b === 0) {
return -1; // 错误码:除零错误
}
result.push(a / b);
return 0; // 成功
}
// 使用方必须检查错误码
let result: number[] = [];
let code: number = divideWithCode(10, 0, result);
if (code !== 0) {
console.log(`错误:除零错误`);
} else {
console.log(`结果:${result[0]}`);
}
错误码的缺点:
- 调用方可能忘记检查错误码
- 错误码含义不直观(-1 代表什么?)
- 多层调用时错误码需要逐层传递
- 正常逻辑和错误处理混在一起
策略二:异常(现代语言倾向)
// 底层抛出异常
function divideWithException(a: number, b: number): number {
if (b === 0) {
throw new Error("除数不能为零");
}
return a / b;
}
// 中间层不处理
function middleLayer(x: number): number {
return divideWithException(x, 0) + 1; // 异常自动向上传
}
// 上层统一处理
try {
middleLayer(10); // 底层的异常传到这里才被捕获
} catch (e) {
console.log(`捕获异常:${String(e)}`);
}
异常的优势:
- 强制处理(不 catch 程序就崩溃)
- 错误信息丰富(可以携带任意信息)
- 自动向上传播(中间层不需要写任何处理代码)
- 正常逻辑和错误处理分离
对比错误码 vs 异常:
| 方式 | 中间层代码 | 特点 |
|---|---|---|
| 错误码 | 必须检查并传递错误码 | 繁琐,容易遗漏 |
| 异常 | 什么都不用写 | 自动传播到上层 |
ArkTS 的选择: 主要使用异常机制,但简单场景也可以用返回值。
第二部分:throw 抛出异常
2.1 throw 语法
基本形式:
// 抛出 Error 对象
throw new Error("错误描述信息");
// 抛出带特定消息的 Error
throw new Error("文件不存在:/data/config.txt");
// 可以在任何地方抛出
function checkAge(age: number): void {
if (age < 0) {
throw new Error("年龄不能为负数");
}
if (age > 150) {
throw new Error("年龄超出合理范围");
}
}
throw 的重要特性:throw 后的代码不再执行
function testThrow(): void {
console.log("第一行");
throw new Error("出错了");
console.log("第二行"); // ❌ 永远不会执行!
console.log("第三行"); // ❌ 永远不会执行!
}
// 调用
testThrow();
// 输出:第一行
// 然后抛出异常,函数立即终止
throw 的行为:
- 立即中断当前函数的执行
- 跳转到调用栈上层的最近 catch 块
- throw 之后的代码永远不会执行
对比 C++:
| 特性 | C++ | ArkTS |
|---|---|---|
| 抛出语法 | throw MyException(); |
throw new Error("msg"); |
| 抛出类型 | 任意类型(推荐异常类) | 必须是 Error 或其子类 |
| 抛出指针 | throw new MyException()(不推荐) |
throw new Error()(标准做法) |
| 执行中断 | 是 | 是 |
2.2 Error 对象
Error 对象的属性:
let err: Error = new Error("这是一个错误");
// message 属性:错误描述
console.log(`message: ${err.message}`); // "这是一个错误"
// name 属性:错误类型名称
console.log(`name: ${err.name}`); // "Error"
创建 Error 的完整形式:
// 只有 message 参数
let err1: Error = new Error("简单错误");
// 查看所有属性
function printError(err: Error): void {
console.log(`错误名称:${err.name}`);
console.log(`错误消息:${err.message}`);
}
对比 C++:
// C++ 标准异常类
std::runtime_error err("这是一个错误");
std::cout << err.what(); // 获取错误消息
// ArkTS 对应
let err: Error = new Error("这是一个错误");
console.log(err.message); // 获取错误消息
2.3 何时该抛出异常
抛出异常的原则:异常应表示"不该发生的情况"
// ✅ 应该抛出异常的情况:违反前置条件
function sqrt(x: number): number {
if (x < 0) {
throw new Error("不能对负数开平方");
}
return Math.sqrt(x);
}
// ✅ 应该抛出异常的情况:违反业务规则
class BankAccount {
private _balance: number = 0;
withdraw(amount: number): void {
if (amount < 0) {
throw new Error("取款金额不能为负数");
}
if (amount > this._balance) {
throw new Error("余额不足");
}
this._balance = this._balance - amount;
}
}
// ✅ 应该抛出异常的情况:资源访问失败
function readConfig(path: string): string {
if (path === "") {
throw new Error("配置文件路径不能为空");
}
// 模拟读取失败
throw new Error(`无法读取文件:${path}`);
}
不应该抛出异常的情况:
// ❌ 不要用异常处理正常流程
function findUser(users: string[], name: string): string {
for (let i = 0; i < users.length; i++) {
if (users[i] === name) {
return users[i];
}
}
throw new Error("用户不存在"); // 不好!找不到是正常情况
}
// ✅ 用返回值表示"找不到"
function findUserBetter(users: string[], name: string): string | null {
for (let i = 0; i < users.length; i++) {
if (users[i] === name) {
return users[i];
}
}
return null; // 找不到返回 null
}
第三部分:try / catch / finally
3.1 基本语法
三个代码块的作用:
// try:包裹可能抛出异常的代码
// catch:捕获并处理异常
// finally:无论是否异常都执行(用于清理资源)
try {
// 可能抛出异常的代码
let result: number = divide(10, 0);
console.log(`结果:${result}`);
} catch (e) {
// 异常处理代码
console.log(`捕获到异常:${String(e)}`);
} finally {
// 清理代码(可选)
console.log("清理资源...");
}
function divide(a: number, b: number): number {
if (b === 0) {
throw new Error("除数不能为零");
}
return a / b;
}
执行流程:
- 正常情况:try → finally
- 异常情况:try(抛出)→ catch → finally
- catch 中再抛出:try(抛出)→ catch(抛出)→ finally → 向上传播
3.2 catch 中的错误对象
获取错误信息:
try {
throw new Error("发生错误");
} catch (e) {
// e 的类型在 ArkTS 中是 Object
// 需要转换为 Error 类型来访问 message
if (e instanceof Error) {
console.log(`错误消息:${e.message}`);
console.log(`错误名称:${e.name}`);
} else {
console.log(`未知错误:${String(e)}`);
}
}
使用 instanceof 判断错误类型:
class ValidationError extends Error {
constructor(message: string) {
super(message);
this.name = "ValidationError";
}
}
class NetworkError extends Error {
constructor(message: string) {
super(message);
this.name = "NetworkError";
}
}
function processError(e: Object): void {
if (e instanceof ValidationError) {
console.log(`验证错误:${e.message}`);
} else if (e instanceof NetworkError) {
console.log(`网络错误:${e.message}`);
} else if (e instanceof Error) {
console.log(`一般错误:${e.message}`);
} else {
console.log(`未知错误:${String(e)}`);
}
}
对比 C++:
// C++ 用引用捕获异常
try {
throw MyException("错误");
} catch (const MyException& e) {
std::cout << e.what();
} catch (const std::exception& e) {
std::cout << e.what();
} catch (...) {
std::cout << "未知异常";
}
// ArkTS 用 instanceof 判断类型
try {
throw new MyError("错误");
} catch (e) {
if (e instanceof MyError) {
console.log(e.message);
} else if (e instanceof Error) {
console.log(e.message);
}
}
3.3 finally 资源清理
finally 的核心特性:必定执行
无论发生什么情况,finally 块一定会执行:
- 正常结束 ✅
- 发生异常 ✅
- try 中有 return ✅
- catch 中重新抛出异常 ✅
function processFile(path: string): void {
let resource: string = "文件句柄";
try {
console.log(`打开资源:${resource}`);
// 处理文件...
if (path === "") {
throw new Error("路径为空");
}
console.log("处理完成");
return; // 即使有 return,finally 也会执行
} catch (e) {
console.log(`处理异常:${String(e)}`);
} finally {
// 无论是否异常、是否有 return,这里都会执行
console.log(`释放资源:${resource}`);
}
}
// 调用测试
processFile(""); // 异常路径
processFile("/data/file.txt"); // 正常路径
输出:
打开资源:文件句柄
处理异常:Error: 路径为空
释放资源:文件句柄
打开资源:文件句柄
处理完成
释放资源:文件句柄
对比 C++ RAII:
// C++ 推荐用 RAII(资源获取即初始化)
class FileHandle {
public:
FileHandle(const string& path) { /* 打开文件 */ }
~FileHandle() { /* 自动关闭文件 */ } // 析构时释放
};
void processFile(const string& path) {
FileHandle fh(path); // 栈对象,退出作用域自动析构
// 处理文件...
} // fh 自动销毁,文件自动关闭
关键区别:
- C++:用析构函数实现自动清理(RAII)
- ArkTS:没有析构函数,用 finally 显式清理
3.4 嵌套 try/catch
多层异常处理:
function level3(): void {
throw new Error("底层错误");
}
function level2(): void {
try {
level3();
} catch (e) {
console.log("level2 捕获到错误,包装后重新抛出");
throw new Error(`level2包装: ${String(e)}`);
}
}
function level1(): void {
try {
level2();
} catch (e) {
console.log(`level1 最终处理:${String(e)}`);
}
}
level1();
输出:
level2 捕获到错误,包装后重新抛出
level1 最终处理:Error: level2包装: Error: 底层错误
异常传播链:
class DatabaseError extends Error {
constructor(message: string, public readonly sql: string) {
super(message);
this.name = "DatabaseError";
}
}
class ServiceError extends Error {
constructor(message: string, public readonly cause: Error) {
super(message);
this.name = "ServiceError";
}
}
function queryDatabase(sql: string): void {
throw new DatabaseError("连接超时", sql);
}
function fetchUserData(userId: number): void {
try {
queryDatabase(`SELECT * FROM users WHERE id=${userId}`);
} catch (e) {
if (e instanceof DatabaseError) {
// 包装成业务异常,保留原始信息
throw new ServiceError("获取用户数据失败", e);
}
throw e;
}
}
try {
fetchUserData(123);
} catch (e) {
if (e instanceof ServiceError) {
console.log(`业务错误:${e.message}`);
console.log(`原始错误:${e.cause.message}`);
console.log(`SQL:${(e.cause as DatabaseError).sql}`);
}
}
第四部分:自定义异常类
4.1 为什么需要自定义异常
问题:所有异常都是 Error,无法区分类型
// 无法区分是哪种错误
try {
// 可能抛出各种错误
connectDatabase();
parseConfig();
validateUser();
} catch (e) {
// 都是 Error,怎么知道是哪个环节出错?
console.log(`错误:${e.message}`);
}
解决方案:自定义异常类
// 每种错误有自己的类型
class DatabaseConnectionError extends Error {
constructor(message: string) {
super(message);
this.name = "DatabaseConnectionError";
}
}
class ConfigParseError extends Error {
constructor(message: string, public readonly filePath: string) {
super(message);
this.name = "ConfigParseError";
}
}
class UserValidationError extends Error {
constructor(message: string, public readonly field: string) {
super(message);
this.name = "UserValidationError";
}
}
4.2 extends Error —— 创建业务异常类
自定义异常的基本结构:
// 基础业务异常
class BusinessError extends Error {
constructor(message: string) {
super(message);
this.name = "BusinessError";
}
}
// 具体的业务异常
class InsufficientBalanceError extends BusinessError {
constructor(
message: string,
public readonly currentBalance: number,
public readonly requiredAmount: number
) {
super(message);
this.name = "InsufficientBalanceError";
}
}
class InvalidAccountError extends BusinessError {
constructor(
message: string,
public readonly accountId: string
) {
super(message);
this.name = "InvalidAccountError";
}
}
// 使用
class BankAccount {
private _balance: number = 100;
private _accountId: string = "ACC001";
withdraw(amount: number): void {
if (amount <= 0) {
throw new BusinessError("取款金额必须大于0");
}
if (amount > this._balance) {
throw new InsufficientBalanceError(
"余额不足",
this._balance,
amount
);
}
this._balance = this._balance - amount;
}
}
// 处理时区分类型
let account: BankAccount = new BankAccount();
try {
account.withdraw(200);
} catch (e) {
if (e instanceof InsufficientBalanceError) {
console.log(`余额不足:当前${e.currentBalance},需要${e.requiredAmount}`);
} else if (e instanceof BusinessError) {
console.log(`业务错误:${e.message}`);
} else {
console.log(`系统错误:${String(e)}`);
}
}
对比 C++:
// C++ 继承 std::exception
class MyException : public std::exception {
private:
std::string msg;
public:
MyException(const std::string& m) : msg(m) {}
const char* what() const noexcept override {
return msg.c_str();
}
};
// ArkTS 继承 Error
class MyError extends Error {
constructor(message: string) {
super(message);
this.name = "MyError";
}
}
4.3 instanceof 区分异常类型
在 catch 中判断异常类型:
class NetworkTimeoutError extends Error {
constructor(message: string, public readonly timeoutMs: number) {
super(message);
this.name = "NetworkTimeoutError";
}
}
class NetworkConnectionError extends Error {
constructor(message: string, public readonly host: string) {
super(message);
this.name = "NetworkConnectionError";
}
}
function handleNetworkRequest(): void {
// 模拟不同的网络错误
let random: number = Math.random();
if (random < 0.33) {
throw new NetworkTimeoutError("连接超时", 5000);
} else if (random < 0.66) {
throw new NetworkConnectionError("连接被拒绝", "api.example.com");
} else {
throw new Error("未知网络错误");
}
}
try {
handleNetworkRequest();
} catch (e) {
// 按类型分别处理
if (e instanceof NetworkTimeoutError) {
console.log(`请求超时(${e.timeoutMs}ms),建议重试`);
} else if (e instanceof NetworkConnectionError) {
console.log(`无法连接到 ${e.host},请检查网络`);
} else if (e instanceof Error) {
console.log(`网络错误:${e.message}`);
}
}
instanceof 的工作原理:
// instanceof 检查原型链
class BaseError extends Error {}
class DerivedError extends BaseError {}
let err: DerivedError = new DerivedError("测试");
console.log(`${err instanceof DerivedError}`); // true
console.log(`${err instanceof BaseError}`); // true(继承链)
console.log(`${err instanceof Error}`); // true(继承链)
console.log(`${err instanceof Object}`); // true(所有类的基类)
4.4 异常层级设计
设计良好的异常层次结构:
// 1. 应用基础异常(所有应用异常的基类)
class ApplicationError extends Error {
constructor(message: string, public readonly code: string) {
super(message);
this.name = "ApplicationError";
}
}
// 2. 领域层异常
class DomainError extends ApplicationError {
constructor(message: string, code: string) {
super(message, code);
this.name = "DomainError";
}
}
// 3. 基础设施层异常
class InfrastructureError extends ApplicationError {
constructor(message: string, code: string) {
super(message, code);
this.name = "InfrastructureError";
}
}
// 4. 具体的领域异常
class InvalidOrderStateError extends DomainError {
constructor(message: string) {
super(message, "ORDER_001");
this.name = "InvalidOrderStateError";
}
}
class ProductOutOfStockError extends DomainError {
constructor(message: string, public readonly productId: string) {
super(message, "ORDER_002");
this.name = "ProductOutOfStockError";
}
}
// 5. 具体的基础设施异常
class DatabaseConnectionError extends InfrastructureError {
constructor(message: string) {
super(message, "DB_001");
this.name = "DatabaseConnectionError";
}
}
class CacheUnavailableError extends InfrastructureError {
constructor(message: string) {
super(message, "CACHE_001");
this.name = "CacheUnavailableError";
}
}
// 使用示例
class OrderService {
placeOrder(productId: string, quantity: number): void {
if (quantity <= 0) {
throw new InvalidOrderStateError("订单数量必须大于0");
}
// 模拟库存检查
if (productId === "OUT_OF_STOCK") {
throw new ProductOutOfStockError("商品缺货", productId);
}
console.log("订单创建成功");
}
}
// 统一异常处理
function handleServiceError(e: Object): void {
if (e instanceof DomainError) {
console.log(`[业务错误 ${e.code}] ${e.message}`);
} else if (e instanceof InfrastructureError) {
console.log(`[系统错误 ${e.code}] ${e.message},请联系管理员`);
} else if (e instanceof Error) {
console.log(`[未知错误] ${e.message}`);
}
}
第五部分:错误处理最佳实践
5.1 何时用异常 vs 何时用返回值
使用异常的场景:
// ✅ 违反前置条件
function sqrt(x: number): number {
if (x < 0) {
throw new Error("不能对负数开平方");
}
return Math.sqrt(x);
}
// ✅ 违反不变量
class Stack<T> {
private items: T[] = [];
pop(): T {
if (this.items.length === 0) {
throw new Error("栈为空");
}
return this.items.pop() as T;
}
}
// ✅ 资源访问失败
function readFile(path: string): string {
if (path === "") {
throw new Error("路径不能为空");
}
// 读取失败抛出异常
throw new Error("文件不存在");
}
使用返回值的场景:
// ✅ 查找可能不存在
function findUserById(users: string[], id: number): string | null {
if (id >= 0 && id < users.length) {
return users[id];
}
return null; // 找不到是正常的
}
// ✅ 解析可能失败
function parseNumber(str: string): number | null {
let num: number = Number(str);
if (isNaN(num)) {
return null; // 解析失败是正常的
}
return num;
}
// ✅ 业务校验结果
function validatePassword(password: string): { valid: boolean; errors: string[] } {
let errors: string[] = [];
if (password.length < 8) {
errors.push("密码至少8位");
}
// ... 其他检查
return { valid: errors.length === 0, errors: errors };
}
决策指南:
| 场景 | 推荐方式 |
|---|---|
| 违反前置条件/不变量 | 抛出异常 |
| 资源访问失败 | 抛出异常 |
| 查找可能不存在 | 返回 null/undefined |
| 解析可能失败 | 返回 null 或结果对象 |
| 业务校验(多错误) | 返回错误列表 |
| 状态转换失败 | 抛出异常 |
5.2 不要忽略异常
❌ 危险的空 catch:
// ❌ 绝对不要这样做!
try {
criticalOperation();
} catch (e) {
// 什么都不做,异常被吞掉了!
}
// ❌ 只打印日志但不处理
try {
saveData();
} catch (e) {
console.log("出错了"); // 程序继续执行,但数据没保存!
}
continueWithInconsistentState(); // 在错误状态下继续!
✅ 正确处理异常:
// ✅ 至少记录详细信息
try {
criticalOperation();
} catch (e) {
console.log(`操作失败:${String(e)}`);
// 根据情况决定是否继续
throw e; // 重新抛出,让上层处理
}
// ✅ 转换为业务结果
try {
let data: string = loadConfig();
processConfig(data);
} catch (e) {
console.log(`加载配置失败,使用默认配置:${String(e)}`);
useDefaultConfig(); // 有备选方案
}
// ✅ 包装后抛出
try {
lowLevelOperation();
} catch (e) {
throw new BusinessError(`业务操作失败:${String(e)}`);
}
5.3 错误信息的设计
好的错误信息:
// ✅ 清晰说明问题
throw new Error("除数不能为零");
// ✅ 包含上下文信息
throw new Error(`用户 ${userId} 不存在`);
// ✅ 提供解决建议
throw new Error(`配置文件 ${path} 不存在,请检查路径或运行 init 命令创建`);
// ✅ 包含相关数据
class ValidationError extends Error {
constructor(
message: string,
public readonly field: string,
public readonly value: string
) {
super(`${field} 验证失败:${message},当前值:${value}`);
this.name = "ValidationError";
}
}
不好的错误信息:
// ❌ 太模糊
throw new Error("出错了");
throw new Error("操作失败");
// ❌ 技术细节暴露给用户
throw new Error("Array index out of bounds: 5");
// ❌ 没有上下文
throw new Error("文件不存在"); // 哪个文件?
5.4 防御性编程
参数校验:
class SafeCalculator {
divide(a: number, b: number): number {
// 防御性校验
if (typeof a !== "number" || typeof b !== "number") {
throw new Error("参数必须是数字");
}
if (b === 0) {
throw new Error("除数不能为零");
}
return a / b;
}
sqrt(x: number): number {
if (x < 0) {
throw new Error("不能对负数开平方");
}
return Math.sqrt(x);
}
}
边界检查:
class SafeArray<T> {
private items: T[] = [];
get(index: number): T {
if (index < 0 || index >= this.items.length) {
throw new Error(`索引 ${index} 超出范围 [0, ${this.items.length})`);
}
return this.items[index];
}
add(item: T): void {
if (item === null || item === undefined) {
throw new Error("不能添加 null 或 undefined");
}
this.items.push(item);
}
}
前置条件检查:
class BankTransfer {
transfer(from: string, to: string, amount: number): void {
// 前置条件检查
this.assertNotEmpty(from, "转出账户");
this.assertNotEmpty(to, "转入账户");
this.assertPositive(amount, "转账金额");
this.assertNotSame(from, to, "转出和转入账户");
// 执行业务逻辑
// ...
}
private assertNotEmpty(value: string, name: string): void {
if (value === "") {
throw new Error(`${name}不能为空`);
}
}
private assertPositive(value: number, name: string): void {
if (value <= 0) {
throw new Error(`${name}必须大于0`);
}
}
private assertNotSame(a: string, b: string, name: string): void {
if (a === b) {
throw new Error(`${name}不能相同`);
}
}
}
第六部分:小结与练习
6.1 知识点对比总结表
| 概念 | ArkTS | C++ |
|---|---|---|
| 抛出异常 | throw new Error("msg") |
throw MyException("msg") |
| 异常基类 | Error |
std::exception |
| 捕获异常 | catch (e) { ... } |
catch (const MyEx& e) { ... } |
| 类型判断 | e instanceof MyError |
多个 catch 块 |
| 自定义异常 | class MyError extends Error |
class MyEx : public std::exception |
| 资源清理 | finally 块 |
RAII(析构函数) |
| 嵌套异常 | 支持 | 支持 |
| 异常规格 | 无 | noexcept |
6.2 核心要点回顾
- throw:抛出 Error 对象或其子类实例
- try/catch/finally:捕获和处理异常,finally 用于清理
- 自定义异常:继承 Error 类,添加业务属性
- instanceof:在 catch 中判断异常类型
- 最佳实践:异常用于"不该发生的情况",不要忽略异常,设计好的错误信息
练习题
练习1:选择题
-
以下哪个是 ArkTS 中抛出异常的正确语法?
- A.
throw "错误信息" - B.
throw new Error("错误信息") - C.
throw Error("错误信息") - D.
throw "Error", "错误信息"
- A.
-
在 catch 块中,如何判断捕获的异常是特定自定义类型?
- A.
if (e.type === "MyError") - B.
if (e instanceof MyError) - C.
if (e.name === "MyError") - D.
if (typeof e === "MyError")
- A.
-
finally 块的特点是?
- A. 只有发生异常时才执行
- B. 只有没有异常时才执行
- C. 无论是否发生异常都会执行
- D. 只有 return 时才执行
练习1答案
答案:1.B 2.B 3.C
| 题号 | 答案 | 解析 |
|---|---|---|
| 1 | B | ArkTS 要求抛出 Error 对象实例,使用 new Error() |
| 2 | B | 使用 instanceof 运算符检查对象类型 |
| 3 | C | finally 块无论是否异常、是否有 return 都会执行 |
练习2:代码补全
补全以下代码,实现一个带异常处理的除法函数:
// 1. 定义自定义异常类 DivideByZeroError
class DivideByZeroError extends ______ {
constructor() {
super("除数不能为零");
this.______ = "DivideByZeroError";
}
}
// 2. 实现 safeDivide 函数
function safeDivide(a: number, b: number): ______ {
if (______ === 0) {
throw new ______();
}
return a / b;
}
// 3. 使用 try/catch 调用
______ {
let result: number = safeDivide(10, 0);
console.log(`结果:${result}`);
} ______ (e) {
if (e ______ DivideByZeroError) {
console.log(`除零错误:${e.message}`);
} else {
console.log(`其他错误:${String(e)}`);
}
}
练习2答案
// 1. 定义自定义异常类 DivideByZeroError
class DivideByZeroError extends Error {
constructor() {
super("除数不能为零");
this.name = "DivideByZeroError";
}
}
// 2. 实现 safeDivide 函数
function safeDivide(a: number, b: number): number {
if (b === 0) {
throw new DivideByZeroError();
}
return a / b;
}
// 3. 使用 try/catch 调用
try {
let result: number = safeDivide(10, 0);
console.log(`结果:${result}`);
} catch (e) {
if (e instanceof DivideByZeroError) {
console.log(`除零错误:${e.message}`);
} else {
console.log(`其他错误:${String(e)}`);
}
}
练习3:编程题 - 实现带异常处理的栈
要求实现一个 SafeStack<T> 类:
- 使用泛型
T - 私有属性
items: T[]存储数据 - 方法
push(item: T): void—— 添加元素 - 方法
pop(): T—— 弹出元素,栈空时抛出StackEmptyError - 方法
peek(): T—— 查看栈顶,栈空时抛出StackEmptyError - getter
isEmpty(): boolean - getter
size(): number
定义自定义异常 StackEmptyError。
练习3答案
// 自定义异常
class StackEmptyError extends Error {
constructor() {
super("栈为空");
this.name = "StackEmptyError";
}
}
// 安全栈实现
class SafeStack<T> {
private items: T[] = [];
push(item: T): void {
this.items.push(item);
}
pop(): T {
if (this.items.length === 0) {
throw new StackEmptyError();
}
// 不使用 as 类型断言,避免使用未学语法
let len = this.items.length;
let e = this.items[len - 1];
this.items.splice(len - 1, 1);
return e;
}
peek(): T {
if (this.items.length === 0) {
throw new StackEmptyError();
}
return this.items[this.items.length - 1];
}
get isEmpty(): boolean {
return this.items.length === 0;
}
get size(): number {
return this.items.length;
}
}
// 使用示例
let stack: SafeStack<number> = new SafeStack<number>();
stack.push(10);
stack.push(20);
console.log(`栈顶:${stack.peek()}`); // 20
console.log(`弹出:${stack.pop()}`); // 20
console.log(`大小:${stack.size}`); // 1
try {
stack.pop();
stack.pop(); // 这里会抛出异常
} catch (e) {
if (e instanceof StackEmptyError) {
console.log(`错误:${e.message}`);
}
}
练习4:编程题 - 实现文件读取器
实现一个 FileReader 类,带异常处理:
- 定义
FileNotFoundError异常(继承 Error) - 定义
PermissionDeniedError异常(继承 Error) - 类
FileReader有私有属性path: string - 构造函数接收
path,如果为空字符串抛出Error - 方法
read(): string,模拟读取:- 如果 path 包含 "notfound" 抛出
FileNotFoundError - 如果 path 包含 "denied" 抛出
PermissionDeniedError - 否则返回 "文件内容"
- 如果 path 包含 "notfound" 抛出
- 使用 try/catch/finally 调用,finally 中打印 "清理资源"
练习4答案
// 自定义异常
class FileNotFoundError extends Error {
constructor(public readonly path: string) {
super(`文件不存在:${path}`);
this.name = "FileNotFoundError";
}
}
class PermissionDeniedError extends Error {
constructor(public readonly path: string) {
super(`没有权限访问:${path}`);
this.name = "PermissionDeniedError";
}
}
// 文件读取器
class FileReader {
private path: string;
constructor(path: string) {
if (path === "") {
throw new Error("文件路径不能为空");
}
this.path = path;
}
read(): string {
if (this.path.indexOf("notfound") >= 0) {
throw new FileNotFoundError(this.path);
}
if (this.path.indexOf("denied") >= 0) {
throw new PermissionDeniedError(this.path);
}
return "文件内容";
}
}
// 使用示例
function readFileSafely(path: string): void {
let reader: FileReader | null = null;
try {
reader = new FileReader(path);
let content: string = reader.read();
console.log(`读取成功:${content}`);
} catch (e) {
if (e instanceof FileNotFoundError) {
console.log(`文件未找到:${e.path}`);
} else if (e instanceof PermissionDeniedError) {
console.log(`权限不足:${e.path}`);
} else if (e instanceof Error) {
console.log(`错误:${e.message}`);
}
} finally {
console.log("清理资源");
}
}
// 测试
readFileSafely("/data/test.txt"); // 正常
readFileSafely("/data/notfound.txt"); // 文件不存在
readFileSafely("/data/denied.txt"); // 权限不足
readFileSafely(""); // 路径为空
练习5:编程题 - 银行账户异常体系
实现一个完整的银行账户异常体系:
- 定义基类
AccountError继承 Error - 定义子类:
InsufficientFundsError(余额不足)—— 属性:currentBalance, requiredAmountInvalidAmountError(无效金额)—— 属性:amountAccountFrozenError(账户冻结)—— 属性:reason
- 实现
BankAccount类:- 属性:balance(余额), frozen(是否冻结)
- 方法:deposit(amount) —— 存款
- 方法:withdraw(amount) —— 取款,可能抛出各种异常
- 编写处理函数,根据异常类型给出不同提示
练习5答案
// 异常体系
class AccountError extends Error {
constructor(message: string) {
super(message);
this.name = "AccountError";
}
}
class InsufficientFundsError extends AccountError {
constructor(
public readonly currentBalance: number,
public readonly requiredAmount: number
) {
super(`余额不足:当前 ${currentBalance},需要 ${requiredAmount}`);
this.name = "InsufficientFundsError";
}
}
class InvalidAmountError extends AccountError {
constructor(public readonly amount: number) {
super(`无效金额:${amount}`);
this.name = "InvalidAmountError";
}
}
class AccountFrozenError extends AccountError {
constructor(public readonly reason: string) {
super(`账户已冻结:${reason}`);
this.name = "AccountFrozenError";
}
}
// 银行账户
class BankAccount {
private _balance: number = 0;
private _frozen: boolean = false;
private _freezeReason: string = "";
get balance(): number {
return this._balance;
}
freeze(reason: string): void {
this._frozen = true;
this._freezeReason = reason;
}
unfreeze(): void {
this._frozen = false;
this._freezeReason = "";
}
deposit(amount: number): void {
if (amount <= 0) {
throw new InvalidAmountError(amount);
}
if (this._frozen) {
throw new AccountFrozenError(this._freezeReason);
}
this._balance = this._balance + amount;
}
withdraw(amount: number): void {
if (amount <= 0) {
throw new InvalidAmountError(amount);
}
if (this._frozen) {
throw new AccountFrozenError(this._freezeReason);
}
if (amount > this._balance) {
throw new InsufficientFundsError(this._balance, amount);
}
this._balance = this._balance - amount;
}
}
// 处理函数
function handleAccountOperation(account: BankAccount, operation: string, amount: number): void {
try {
if (operation === "deposit") {
account.deposit(amount);
console.log(`存款 ${amount} 成功,当前余额:${account.balance}`);
} else if (operation === "withdraw") {
account.withdraw(amount);
console.log(`取款 ${amount} 成功,当前余额:${account.balance}`);
}
} catch (e) {
if (e instanceof InsufficientFundsError) {
console.log(`余额不足,当前:${e.currentBalance},需要:${e.requiredAmount}`);
} else if (e instanceof InvalidAmountError) {
console.log(`金额无效:${e.amount}`);
} else if (e instanceof AccountFrozenError) {
console.log(`账户冻结:${e.reason}`);
} else {
console.log(`未知错误:${String(e)}`);
}
}
}
// 测试
let account: BankAccount = new BankAccount();
account.deposit(1000);
handleAccountOperation(account, "withdraw", 500); // 成功
handleAccountOperation(account, "withdraw", 1000); // 余额不足
handleAccountOperation(account, "withdraw", -100); // 无效金额
account.freeze("涉嫌欺诈");
handleAccountOperation(account, "withdraw", 100); // 账户冻结
练习6:编程题 - 异常包装与链
实现异常包装,保留原始异常信息:
- 定义
DataAccessError异常,有属性cause: Error表示原始异常 - 定义
ServiceError异常,有属性cause: Error表示原始异常 - 模拟三层调用:
queryDatabase():可能抛出 DatabaseConnectionErrorfetchUser():调用 queryDatabase,捕获后包装为 DataAccessErrorgetUserProfile():调用 fetchUser,捕获后包装为 ServiceError
- 最终处理时,能够获取完整的异常链信息
练习6答案
// 底层异常
class DatabaseConnectionError extends Error {
constructor(message: string) {
super(message);
this.name = "DatabaseConnectionError";
}
}
// 包装异常
class DataAccessError extends Error {
constructor(
message: string,
public readonly cause: Error
) {
super(message);
this.name = "DataAccessError";
}
}
class ServiceError extends Error {
constructor(
message: string,
public readonly cause: Error
) {
super(message);
this.name = "ServiceError";
}
}
// 模拟三层调用
function queryDatabase(): void {
throw new DatabaseConnectionError("连接超时");
}
function fetchUser(userId: number): void {
try {
queryDatabase();
} catch (e) {
if (e instanceof Error) {
throw new DataAccessError(`获取用户 ${userId} 失败`, e);
}
throw e;
}
}
function getUserProfile(userId: number): void {
try {
fetchUser(userId);
} catch (e) {
if (e instanceof Error) {
throw new ServiceError("获取用户资料失败", e);
}
throw e;
}
}
// 处理并打印异常链
function printErrorChain(e: Error, level: number = 0): void {
let indent: string = "";
for (let i = 0; i < level; i++) {
indent = indent + " ";
}
console.log(`${indent}[${e.name}] ${e.message}`);
// 检查是否有 cause 属性
let cause = (e as ServiceError | DataAccessError).cause;
if (cause !== undefined && cause instanceof Error) {
printErrorChain(cause, level + 1);
}
}
// 测试
try {
getUserProfile(123);
} catch (e) {
if (e instanceof Error) {
printErrorChain(e);
}
}
// 输出:
// [ServiceError] 获取用户资料失败
// [DataAccessError] 获取用户 123 失败
// [DatabaseConnectionError] 连接超时
练习7:编程题 - 防御性编程实践
实现一个 SafeArrayUtils 工具类,包含以下方法,每个方法都要进行参数校验:
getElement<T>(arr: T[], index: number): T—— 获取元素,检查索引范围setElement<T>(arr: T[], index: number, value: T): void—— 设置元素slice<T>(arr: T[], start: number, end: number): T[]—— 切片,检查参数findIndex<T>(arr: T[], predicate: (item: T) => boolean): number—— 查找索引
每个方法都要抛出有意义的异常。
练习7答案
// 工具异常
class ArrayIndexError extends Error {
constructor(index: number, length: number) {
super(`索引 ${index} 超出范围 [0, ${length})`);
this.name = "ArrayIndexError";
}
}
class ArrayArgumentError extends Error {
constructor(message: string) {
super(message);
this.name = "ArrayArgumentError";
}
}
// 安全的数组工具类
class SafeArrayUtils {
static getElement<T>(arr: T[], index: number): T {
// 检查数组
if (arr === null || arr === undefined) {
throw new ArrayArgumentError("数组不能为 null 或 undefined");
}
// 检查索引类型
if (typeof index !== "number" || isNaN(index)) {
throw new ArrayArgumentError(`索引必须是有效数字:${index}`);
}
// 检查索引范围
if (index < 0 || index >= arr.length) {
throw new ArrayIndexError(index, arr.length);
}
return arr[index];
}
static setElement<T>(arr: T[], index: number, value: T): void {
if (arr === null || arr === undefined) {
throw new ArrayArgumentError("数组不能为 null 或 undefined");
}
if (typeof index !== "number" || isNaN(index)) {
throw new ArrayArgumentError(`索引必须是有效数字:${index}`);
}
if (index < 0 || index >= arr.length) {
throw new ArrayIndexError(index, arr.length);
}
arr[index] = value;
}
static slice<T>(arr: T[], start: number, end: number): T[] {
if (arr === null || arr === undefined) {
throw new ArrayArgumentError("数组不能为 null 或 undefined");
}
if (typeof start !== "number" || isNaN(start)) {
throw new ArrayArgumentError(`start 必须是有效数字:${start}`);
}
if (typeof end !== "number" || isNaN(end)) {
throw new ArrayArgumentError(`end 必须是有效数字:${end}`);
}
if (start < 0 || start > arr.length) {
throw new ArrayArgumentError(`start ${start} 超出范围 [0, ${arr.length}]`);
}
if (end < start || end > arr.length) {
throw new ArrayArgumentError(`end ${end} 超出范围 [${start}, ${arr.length}]`);
}
let result: T[] = [];
for (let i = start; i < end; i++) {
result.push(arr[i]);
}
return result;
}
static findIndex<T>(arr: T[], predicate: (item: T) => boolean): number {
if (arr === null || arr === undefined) {
throw new ArrayArgumentError("数组不能为 null 或 undefined");
}
if (typeof predicate !== "function") {
throw new ArrayArgumentError("predicate 必须是函数");
}
for (let i = 0; i < arr.length; i++) {
if (predicate(arr[i])) {
return i;
}
}
return -1;
}
}
// 测试
let arr: number[] = [10, 20, 30, 40, 50];
console.log(`getElement(2): ${SafeArrayUtils.getElement(arr, 2)}`); // 30
SafeArrayUtils.setElement(arr, 1, 25);
console.log(`setElement 后: ${arr}`); // [10, 25, 30, 40, 50]
let sliced: number[] = SafeArrayUtils.slice(arr, 1, 4);
console.log(`slice(1,4): ${sliced}`); // [25, 30, 40]
let found: number = SafeArrayUtils.findIndex(arr, (item: number): boolean => item === 30);
console.log(`findIndex(30): ${found}`); // 2
// 测试异常
try {
SafeArrayUtils.getElement(arr, 10);
} catch (e) {
console.log(`错误:${String(e)}`);
}
练习8:综合题 - 实现带异常处理的配置管理器
实现一个完整的配置管理器,包含:
-
异常体系:
ConfigError(基类)ConfigFileNotFoundError(文件不存在)ConfigParseError(解析失败,属性:lineNumber)ConfigValidationError(验证失败,属性:field, expectedValue)
-
ConfigManager类:- 私有属性
config: Map<string, string> - 方法
loadFromFile(path: string): void—— 加载配置 - 方法
get(key: string): string—— 获取值,不存在抛出异常 - 方法
getInt(key: string): number—— 获取整数值,解析失败抛出异常 - 方法
validateRequired(keys: string[]): void—— 验证必需键
- 私有属性
-
使用 try/catch/finally 调用,finally 中记录日志
练习8答案
// 异常体系
class ConfigError extends Error {
constructor(message: string) {
super(message);
this.name = "ConfigError";
}
}
class ConfigFileNotFoundError extends ConfigError {
constructor(public readonly path: string) {
super(`配置文件不存在:${path}`);
this.name = "ConfigFileNotFoundError";
}
}
class ConfigParseError extends ConfigError {
constructor(
message: string,
public readonly lineNumber: number
) {
super(`第 ${lineNumber} 行解析错误:${message}`);
this.name = "ConfigParseError";
}
}
class ConfigValidationError extends ConfigError {
constructor(
message: string,
public readonly field: string,
public readonly expectedValue: string
) {
super(`${field} 验证失败:${message},期望值:${expectedValue}`);
this.name = "ConfigValidationError";
}
}
class ConfigKeyNotFoundError extends ConfigError {
constructor(public readonly key: string) {
super(`配置项不存在:${key}`);
this.name = "ConfigKeyNotFoundError";
}
}
// 配置管理器
class ConfigManager {
private config: Map<string, string> = new Map<string, string>();
loadFromFile(path: string): void {
// 模拟文件检查
if (path.indexOf("notfound") >= 0) {
throw new ConfigFileNotFoundError(path);
}
// 模拟解析
if (path.indexOf("parseerror") >= 0) {
throw new ConfigParseError("格式错误", 5);
}
// 模拟加载成功
this.config.set("host", "localhost");
this.config.set("port", "8080");
this.config.set("timeout", "30");
}
get(key: string): string {
if (!this.config.has(key)) {
throw new ConfigKeyNotFoundError(key);
}
return this.config.get(key) as string;
}
getInt(key: string): number {
let value: string = this.get(key);
let num: number = parseInt(value, 10);
if (isNaN(num)) {
throw new ConfigParseError(`"${value}" 不是有效整数`, 0);
}
return num;
}
validateRequired(keys: string[]): void {
for (let i = 0; i < keys.length; i++) {
let key: string = keys[i];
if (!this.config.has(key)) {
throw new ConfigValidationError(
"缺少必需配置项",
key,
"存在"
);
}
}
}
set(key: string, value: string): void {
this.config.set(key, value);
}
}
// 使用示例
function loadConfiguration(path: string): ConfigManager | null {
let manager: ConfigManager = new ConfigManager();
try {
console.log(`开始加载配置:${path}`);
manager.loadFromFile(path);
manager.validateRequired(["host", "port"]);
let host: string = manager.get("host");
let port: number = manager.getInt("port");
console.log(`配置加载成功:${host}:${port}`);
return manager;
} catch (e) {
if (e instanceof ConfigFileNotFoundError) {
console.log(`配置文件缺失:${e.path}`);
} else if (e instanceof ConfigParseError) {
console.log(`解析错误:${e.message}`);
} else if (e instanceof ConfigValidationError) {
console.log(`验证失败:${e.field} ${e.message}`);
} else if (e instanceof ConfigError) {
console.log(`配置错误:${e.message}`);
} else {
console.log(`未知错误:${String(e)}`);
}
return null;
} finally {
console.log("配置加载操作完成");
}
}
// 测试
loadConfiguration("/app/config.txt"); // 正常
loadConfiguration("/app/notfound.txt"); // 文件不存在
loadConfiguration("/app/parseerror.txt"); // 解析错误
附录:ArkTS 错误处理速查表
// 1. 抛出异常
throw new Error("错误信息");
throw new MyCustomError("错误信息", extraData);
// 2. 自定义异常
class MyError extends Error {
constructor(message: string, public readonly code: number) {
super(message);
this.name = "MyError";
}
}
// 3. 捕获异常
try {
riskyOperation();
} catch (e) {
if (e instanceof MyError) {
console.log(`我的错误:${e.message},代码:${e.code}`);
} else if (e instanceof Error) {
console.log(`一般错误:${e.message}`);
} else {
console.log(`未知错误:${String(e)}`);
}
} finally {
// 清理资源
}
// 4. 异常包装
try {
lowLevelOperation();
} catch (e) {
if (e instanceof Error) {
throw new HighLevelError("包装", e);
}
throw e;
}
// 5. 防御性编程
function safeDivide(a: number, b: number): number {
if (typeof a !== "number" || typeof b !== "number") {
throw new Error("参数必须是数字");
}
if (b === 0) {
throw new Error("除数不能为零");
}
return a / b;
}

浙公网安备 33010602011771号