javascript技巧

关注公众号 jb51net

关闭
首页 > 网络编程 > JavaScript > javascript技巧 > JS错误处理UnError

UnError如何让JavaScript错误处理更优雅详解

作者:会功夫的李白

UnError是一个现代化的JavaScript错误处理库,解决标准Error的诸多痛点,这篇文章主要介绍了UnError如何让JavaScript错误处理更优雅的相关资料,文中通过代码介绍的非常详细,需要的朋友可以参考下

一个轻量级、类型安全的统一错误处理库

痛点:JavaScript 错误处理的困境

作为一名 JavaScript/TypeScript 开发者,你一定遇到过这些令人头疼的问题:

问题 1:错误信息贫乏

throw new Error('User not found');

这个错误告诉了我们什么?只有一句话。我们不知道:

问题 2:错误链追踪困难

try {
  await database.connect();
} catch (err) {
  // 原始错误信息丢失了!
  throw new Error('Failed to fetch user');
}

当错误在多层调用中传递时,我们往往会丢失原始的错误信息。

问题 3:错误无法序列化

const error = new Error('Something went wrong');
const json = JSON.stringify(error);
console.log(json); // "{}" - 空对象!

标准的 Error 对象无法被 JSON 序列化,这在分布式系统中是个大问题。

问题 4:微服务间错误传递麻烦

在微服务架构中,错误需要在不同服务之间传递。但标准 Error 对象:

解决方案:UnError

UnError 是一个现代化的 JavaScript 错误处理库,它解决了标准 Error 的诸多痛点:

无论你是在开发单体应用还是微服务架构,UnError 都能让你的错误处理更加优雅和高效。

快速开始

安装

npm install unerror

基本用法

import { UnError, createError, error } from 'unerror';

// 创建错误
const error1 = new UnError('User not found');

// 带因果错误
try {
  await database.query('SELECT * FROM users');
} catch (err) {
  throw new UnError('Failed to fetch user', err);
  // 你甚至可以
  throw createError('Failed to fetch user', err);
  // 或
  throw error('Failed to fetch user', err);
}

核心功能详解

1. 错误链追踪

UnError 最强大的功能之一就是错误链追踪。它可以完整记录错误的传播路径:

// 第一层:数据库错误
const dbError = new Error('Connection timeout');

// 第二层:查询错误
const queryError = new UnError('Query execution failed', dbError);

// 第三层:服务错误
const serviceError = new UnError('User service error', queryError);

// 第四层:API 错误
const apiError = new UnError('API request failed', serviceError);

// 获取完整的错误链
const chain = apiError.getErrorChain();
console.log(`错误链深度: ${chain.length}`); // 4

// 遍历错误链
chain.forEach((err, index) => {
  console.log(`[${index}] ${err.message}`);
});

输出:

[0] API request failed
[1] User service error
[2] Query execution failed
[3] Connection timeout

2. 序列化和反序列化

UnError 可以完美地序列化为 JSON,这对分布式系统至关重要:

// 服务 A:创建错误并序列化
const error = new UnError('Database connection failed');
const json = JSON.stringify(error.toJSON());

// 通过网络传输到服务 B...
await fetch('/api/log', {
  method: 'POST',
  body: json
});

// 服务 B:接收并反序列化
const data = JSON.parse(receivedJson);
const restored = UnError.fromJSON(data);

// 完整保留了所有信息
console.log(restored.message);    // 'Database connection failed'
console.log(restored.timestamp);  // 原始时间戳
console.log(restored.stack);      // 原始堆栈跟踪

3. 格式化输出

UnError 提供了多种格式化选项,让错误信息更易读:

const cause = new Error('Database connection failed');
const error2 = new UnError('Query execution failed', cause);

// 基本格式化
console.log(error.format());

输出:

UnError: Query execution failed
  Stack:
    at <anonymous> (d:\developer\GitProjects\unerror\examples\04-formatting.ts:30:16)
    at ModuleJob.run (node:internal/modules/esm/module_job:195:25)
    at async ModuleLoader.import (node:internal/modules/esm/loader:337:24)
    at async loadESM (node:internal/process/esm_loader:34:7)
    at async handleMainPromise (node:internal/modules/run_main:106:12)
  Caused by:
    Error: Database connection failed

格式化整个错误链:

console.log(error.formatChain({ includeStack: false }));

输出:

UnError: Query execution failed
  ↳ Error: Database connection failed

4. 堆栈跟踪解析

UnError 可以解析堆栈跟踪,提取结构化信息:

const error = new UnError('Something went wrong');
const frames = error.parseStack();

frames.forEach(frame => {
  console.log(`函数: ${frame.functionName}`);
  console.log(`文件: ${frame.fileName}`);
  console.log(`行号: ${frame.lineNumber}`);
  console.log(`列号: ${frame.columnNumber}`);
});

5. 自定义错误类

使用工厂函数快速创建自定义错误类:

import { createErrorClass } from 'unerror';

// 创建自定义错误类
const DatabaseError = createErrorClass('DatabaseError');
const NetworkError = createErrorClass('NetworkError');
const ValidationError = createErrorClass('ValidationError');

// 使用自定义错误类
const dbError = new DatabaseError('Connection failed');
console.log(dbError.name);                    // 'DatabaseError'
console.log(dbError instanceof UnError);      // true
console.log(dbError instanceof DatabaseError); // true

// 自动继承所有 UnError 功能
console.log(dbError.toJSON());
console.log(dbError.format());

实战场景

场景 1:Express API 错误处理

import express from 'express';
import { UnError } from 'unerror';

const app = express();

// 业务逻辑
async function getUserById(id: string) {
  try {
    const user = await database.findUser(id);
    if (!user) {
      throw new UnError('User not found');
    }
    return user;
  } catch (err) {
    throw new UnError('Failed to get user', err as Error);
  }
}

// 路由处理
app.get('/users/:id', async (req, res) => {
  try {
    const user = await getUserById(req.params.id);
    res.json(user);
  } catch (err) {
    if (UnError.isUnError(err)) {
      // 记录完整的错误链
      console.error(err.formatChain());
      
      // 返回友好的错误信息
      res.status(500).json({
        error: err.message,
        timestamp: err.timestamp
      });
    } else {
      res.status(500).json({ error: 'Internal server error' });
    }
  }
});

场景 2:错误监控和分析

import { UnError } from 'unerror';

class ErrorMonitor {
  private errors: UnError[] = [];
  
  track(error: Error | UnError): void {
    if (UnError.isUnError(error)) {
      this.errors.push(error);
      this.analyze(error);
    }
  }
  
  analyze(error: UnError): void {
    const chain = error.getErrorChain();
    const frames = error.parseStack();
    
    console.log('=== 错误分析报告 ===');
    console.log(`时间: ${new Date(error.timestamp).toISOString()}`);
    console.log(`消息: ${error.message}`);
    console.log(`错误链深度: ${chain.length}`);
    
    if (frames.length > 0) {
      const topFrame = frames[0];
      console.log(`发生位置: ${topFrame.fileName}:${topFrame.lineNumber}`);
      console.log(`函数: ${topFrame.functionName || '(anonymous)'}`);
    }
    
    console.log('\n错误链:');
    chain.forEach((err, index) => {
      console.log(`  ${index + 1}. ${err.message}`);
    });
    
    // 发送到监控服务
    this.sendToMonitoring(error);
  }
  
  sendToMonitoring(error: UnError): void {
    fetch('https://monitoring.example.com/errors', {
      method: 'POST',
      headers: { 'Content-Type': 'application/json' },
      body: JSON.stringify(error.toJSON())
    });
  }
  
  getStatistics() {
    return {
      total: this.errors.length,
      byHour: this.groupByHour(),
      topErrors: this.getTopErrors()
    };
  }
  
  private groupByHour() {
    // 按小时分组统计
    const groups = new Map<string, number>();
    this.errors.forEach(error => {
      const hour = new Date(error.timestamp).toISOString().slice(0, 13);
      groups.set(hour, (groups.get(hour) || 0) + 1);
    });
    return Object.fromEntries(groups);
  }
  
  private getTopErrors() {
    // 统计最常见的错误
    const counts = new Map<string, number>();
    this.errors.forEach(error => {
      counts.set(error.message, (counts.get(error.message) || 0) + 1);
    });
    return Array.from(counts.entries())
      .sort((a, b) => b[1] - a[1])
      .slice(0, 10);
  }
}

// 使用
const monitor = new ErrorMonitor();

try {
  await someOperation();
} catch (err) {
  const error = new UnError('Operation failed', err as Error);
  monitor.track(error);
}

与原 Error 对比

vs 标准 Error

特性标准 ErrorUnError
基本错误信息
堆栈跟踪
时间戳
错误链
序列化
格式化输出
堆栈解析
TypeScript 支持基础完整

vs 手动实现

// ❌ 手动实现 - 需要大量代码
class CustomError extends Error {
  public timestamp: number;
  public cause?: Error;
  
  constructor(message: string, cause?: Error) {
    super(message);
    this.name = 'CustomError';
    this.timestamp = Date.now();
    this.cause = cause;
  }
  
  toJSON() {
    // 需要手动实现序列化
    return {
      name: this.name,
      message: this.message,
      timestamp: this.timestamp,
      stack: this.stack,
      cause: this.cause ? /* 递归序列化 */ : undefined
    };
  }
  
  format() {
    // 需要手动实现格式化
    // ...
  }
  
  // 还需要实现 fromJSON、getErrorChain、parseStack 等方法
}

// ✅ UnError - 开箱即用
import { UnError } from 'unerror';
const error = new UnError('Something went wrong');
// 所有功能都已内置

技术细节

类型安全

UnError 使用 TypeScript 编写,提供完整的类型定义:

import type {
  UnError,
  SerializedError,
  FormatOptions,
  StackFrame
} from 'unerror';

// 类型守卫
function handleError(error: unknown): void {
  if (UnError.isUnError(error)) {
    // TypeScript 知道这里 error 是 UnError 类型
    const timestamp: number = error.timestamp;
    const chain: Array<Error | UnError> = error.getErrorChain();
    const formatted: string = error.format();
  }
}

// 类型注解
const options: FormatOptions = {
  includeStack: true,
  includeCause: true,
  indent: 2
};

const error: UnError = new UnError('Test');
const serialized: SerializedError = error.toJSON();

性能优化

UnError 在设计时充分考虑了性能:

  1. 轻量级:核心代码不到 500 行(未压缩,且包含注释,注释包含示例)
  2. 零依赖:不引入任何第三方库
  3. 按需计算:堆栈解析、格式化等操作只在调用时执行
  4. 高效序列化:使用原生 JSON 序列化,性能优异

测试覆盖

UnError 拥有完善的测试体系:

# 运行测试
npm test

# 查看覆盖率
npm run test:coverage

测试统计:

运行示例

项目包含 7 个完整的示例,展示各种使用场景:

# 运行所有示例
npm run examples

# 运行单个示例
npm run example examples/01-basic-usage.ts      # 基本用法
npm run example examples/02-error-chain.ts      # 错误链
npm run example examples/03-serialization.ts    # 序列化
npm run example examples/04-formatting.ts       # 格式化
npm run example examples/05-stack-parsing.ts    # 堆栈解析
npm run example examples/06-real-world.ts       # 真实场景
npm run example examples/07-prototype-demo.ts   # 原型链演示

总结 

到此这篇关于UnError如何让JavaScript错误处理更优雅的文章就介绍到这了,更多相关JS错误处理UnError内容请搜索脚本之家以前的文章或继续浏览下面的相关文章希望大家以后多多支持脚本之家!

您可能感兴趣的文章:
阅读全文