ARTICLE DETAIL

资讯详情

深耕网站建设、视觉设计与SEO优化的一线实战洞察。

JavaScript/TypeScript 异常处理进阶:从装饰器到错误边界

JavaScript/TypeScript 异常处理进阶:从装饰器到错误边界

1. 为什么我们需要摆脱手动 try-catch

在十多年的编码生涯里,我见过太多这样的代码块:

try { const result = someRiskyOperation(); // 业务逻辑代码... } catch (error) { console.error('操作失败:', error); // 可能还有各种错误处理分支 }

这种模式的问题在于:它把业务逻辑和错误处理强耦合在一起。当项目规模扩大时,你会发现:

  • 代码中遍布着重复的异常处理逻辑
  • 核心业务逻辑被错误处理代码淹没
  • 不同类型的错误难以统一管理
  • 测试用例变得臃肿难维护

2. 现代异常处理方案解析

2.1 装饰器方案

在 TypeScript/JavaScript 生态中,装饰器提供了一种优雅的解决方案:

function errorHandler(target: any, key: string, descriptor: PropertyDescriptor) { const originalMethod = descriptor.value; descriptor.value = async function(...args: any[]) { try { return await originalMethod.apply(this, args); } catch (error) { // 统一的错误处理逻辑 this.handleError(error); } }; return descriptor; } class APIService { @errorHandler async fetchData() { // 业务逻辑... } private handleError(error: Error) { // 集中式错误处理 } }

实际项目中,我们可以扩展这个装饰器,支持:

  • 错误类型分类处理
  • 自动重试机制
  • 错误上报集成

2.2 函数式方案

对于函数式编程风格,我们可以实现高阶函数:

const withErrorHandling = (fn) => async (...args) => { try { return await fn(...args); } catch (error) { // 可以在这里添加上下文信息 error.context = { args }; throw error; // 或者返回统一的错误格式 } }; // 使用示例 const safeFetchUser = withErrorHandling(async (userId) => { // 原始业务逻辑 });

这种方案的优点在于:

  • 保持函数的纯净性
  • 方便组合和重用
  • 适合函数式编程范式

3. 高级错误处理模式

3.1 错误分类系统

建立清晰的错误类型体系至关重要:

class AppError extends Error { constructor( public readonly code: string, public readonly details?: Record<string, any> ) { super(); } } class NetworkError extends AppError { constructor(details?: { url: string; status: number }) { super('NETWORK_ERROR', details); } } // 使用示例 throw new NetworkError({ url: '/api/users', status: 500 });

3.2 错误边界模式

借鉴 React 的错误边界概念,我们可以实现:

class ErrorBoundary { static async execute<T>(fn: () => Promise<T>): Promise<T | ErrorResponse> { try { return await fn(); } catch (error) { if (error instanceof AppError) { return this.handleKnownError(error); } return this.handleUnknownError(error); } } private static handleKnownError(error: AppError) { // 根据错误类型返回特定响应 } }

4. 实战中的经验技巧

4.1 错误上下文增强

在异步操作中,错误经常丢失关键上下文。解决方案:

async function withContext(fn, context) { try { return await fn(); } catch (error) { error.context = { ...error.context, ...context }; throw error; } } // 使用示例 await withContext(() => fetchUser(userId), { userId, requestId: generateId() });

4.2 错误恢复策略

对于可恢复错误,实现智能重试:

async function withRetry<T>( fn: () => Promise<T>, options: { maxRetries: number; retryDelay: number; shouldRetry: (error: Error) => boolean; } ): Promise<T> { let attempts = 0; while (true) { try { return await fn(); } catch (error) { if (attempts >= options.maxRetries || !options.shouldRetry(error)) { throw error; } await new Promise(resolve => setTimeout(resolve, options.retryDelay) ); attempts++; } } }

5. 测试策略优化

5.1 错误注入测试

describe('API Service', () => { it('should handle network errors', async () => { const mockFetch = jest.fn().mockRejectedValue( new NetworkError({ status: 500 }) ); const service = new APIService(mockFetch); const result = await service.fetchData(); expect(result).toEqual({ success: false, code: 'NETWORK_ERROR' }); }); });

5.2 错误覆盖率检查

配置 Jest 或其它测试框架,确保:

  • 所有定义的错误类型都被测试用例覆盖
  • 每个错误分支都有对应的测试
  • 错误消息格式符合规范

6. 监控与日志集成

6.1 结构化错误日志

interface ErrorLog { timestamp: string; code: string; message: string; stack?: string; context?: Record<string, any>; severity: 'low' | 'medium' | 'high'; } function logError(error: Error): ErrorLog { const entry: ErrorLog = { timestamp: new Date().toISOString(), message: error.message, severity: 'medium' }; if (error instanceof AppError) { entry.code = error.code; entry.context = error.details; } // 发送到日志服务 logger.send(entry); return entry; }

6.2 错误聚合分析

使用 Sentry 或类似工具时,配置:

  • 错误分组规则
  • 严重程度自动分类
  • 上下文信息自动附加
  • 用户反馈收集

7. 性能考量

错误处理引入的性能开销主要来自:

  • 调用栈收集
  • 上下文序列化
  • 日志写入

优化建议:

  • 生产环境限制调用栈深度
  • 异步非阻塞的日志写入
  • 敏感信息过滤前置

8. 渐进式迁移策略

对于已有项目,推荐迁移路径:

  1. 先在新代码中使用新方案
  2. 逐步重构高频修改的模块
  3. 最后处理稳定不变的遗留代码
  4. 建立代码规范防止倒退

关键指标监控:

  • 未处理错误数量
  • 错误处理代码重复率
  • 错误解决平均时间
返回列表