TypeScript 错误处理:try-catch 与自定义错误
在 TypeScript 开发中,直接使用 try-catch 捕获 any 或 unknown 类型的错误往往导致代码难以维护。为了精准区分业务逻辑失败、网络异常或输入校验错误,我们需要构建一套基于自定义错误类型的处理机制。
1. 理解 try-catch 的基础陷阱
TypeScript 中,catch 块接收的默认错误类型是 unknown。这意味着如果不进行类型判断,直接访问 error.message 会导致编译报错。
编写 基础的错误捕获代码,观察类型限制:
try {
// 模拟一个可能会抛出错误的函数
JSON.parse('{ invalid json }');
} catch (error) {
// 此时 error 的类型是 unknown
console.log(error.message);
// 错误: 对象的类型为 "unknown"
}
使用 类型断言或类型守卫来消除错误:
try {
JSON.parse('{ invalid json }');
} catch (error) {
// 简单判断是否存在 message 属性
if (error instanceof Error) {
console.log(error.message);
} else {
console.log('未知错误');
}
}
这种方式虽然能解决编译错误,但无法区分错误的具体业务含义。
2. 构建自定义错误类体系
为了区分“数据库连接失败”、“用户未授权”或“参数不合法”,我们需要创建 继承自 Error 的自定义类。
定义 一个基础的自定义错误类 AppError,并添加 code 属性用于存储错误码:
class AppError extends Error {
constructor(
public message: string,
public code: number
) {
super(message);
this.name = 'AppError';
// 维护原型链,确保 instanceof 正常工作
Object.setPrototypeOf(this, AppError.prototype);
}
}
扩展 具体的业务错误类,例如 ValidationError(校验错误)和 NetworkError(网络错误):
// 校验错误:通常由客户端输入引起
class ValidationError extends AppError {
constructor(message: string) {
super(message, 400); // HTTP 400 状态码
this.name = 'ValidationError';
}
}
// 网络错误:通常由后端或网络连接引起
class NetworkError extends AppError {
constructor(message: string) {
super(message, 503); // HTTP 503 状态码
this.name = 'NetworkError';
}
}
3. 错误处理流程设计
在实际应用中,当错误发生时,系统需要根据错误类型执行不同的逻辑(如重试、提示用户或记录日志)。以下流程展示了从抛出错误到最终处理的决策路径。
4. 实施类型守卫与错误处理
为了让 TypeScript 能够在 catch 块中自动收窄 错误类型,我们需要使用 instanceof 作为类型守卫。
模拟 一个可能抛出不同错误的业务函数:
function processUserData(data: any) {
if (!data.name) {
// 抛出校验错误
throw new ValidationError('姓名字段不能为空');
}
if (!data.id) {
// 模拟一个网络请求失败
throw new NetworkError('无法获取用户 ID');
}
return `Processing ${data.name}`;
}
```
**编写** 能够精准处理上述错误的 `try-catch` 逻辑:
```typescript
try {
const result = processUserData({ name: '' });
console.log(result);
} catch (error) {
// 使用 instanceof 进行类型守卫
if (error instanceof ValidationError) {
// TypeScript 此处知道 error 是 ValidationError 类型
console.error(`客户端错误 (${error.code}): ${error.message}`);
// 执行 UI 提示操作
} else if (error instanceof NetworkError) {
// TypeScript 此处知道 error 是 NetworkError 类型
console.error(`服务端错误 (${error.code}): ${error.message}`);
// 执行重试逻辑
} else if (error instanceof Error) {
// 处理其他标准错误
console.error(`系统意外错误: ${error.message}`);
} else {
// 处理非 Error 对象抛出的异常(如 throw 'string')
console.error('未知异常类型', error);
}
}
5. 异步函数中的错误处理
在 async/await 语法中,错误同样会被 catch 捕获。确保 所有的 Promise 都被正确包裹。
编写 异步业务函数:
async function fetchUserConfig() {
// 模拟异步操作
await new Promise(resolve => setTimeout(resolve, 100));
throw new NetworkError('配置服务连接超时');
}
调用 异步函数并处理错误:
async function main() {
try {
await fetchUserConfig();
} catch (error) {
if (error instanceof NetworkError) {
console.warn('配置加载失败,使用本地缓存');
} else {
console.error('无法恢复的错误');
}
}
}
6. 不同错误类型的策略对比
在系统设计中,不同类型的错误应对应不同的处理策略。下表列出了常见错误类型的处理建议。
| 错误类型 | 推荐处理动作 | 是否需要用户干预 |
|---|---|---|
ValidationError |
修正 输入数据并重试 | 是 |
NetworkError |
等待 短暂延迟后重试 | 否 |
AuthenticationError |
跳转 至登录页面 | 是 |
CriticalSystemError |
记录 日志并上报监控 | 否 |
注意:不要在 catch 块中吞掉错误(即留空 catch {}),除非你明确知道该错误是可以被安全忽略的。
执行 最终的错误处理代码逻辑:
class AuthenticationError extends AppError {
constructor() {
super('Token expired', 401);
this.name = 'AuthenticationError';
}
}
async function secureRequest() {
try {
// 业务逻辑...
throw new AuthenticationError();
} catch (error) {
if (error instanceof AuthenticationError) {
// 清除本地 Token
localStorage.removeItem('token');
// 重定向
window.location.href = '/login';
} else {
// 对于非认证错误,继续向上抛出或处理
throw error;
}
}
}
暂无评论,快来抢沙发吧!