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 的诸多痛点:
- 🎯 简洁的 API - 学习成本低,上手快
- 🔗 完整的错误链 - 追踪错误的来龙去脉
- 📦 可序列化 - 完美支持分布式系统
- 🎨 格式化输出 - 人类可读的错误描述
- 🔧 类型安全 - 完整的 TypeScript 支持
- 🌐 零依赖 - 轻量级,不引入额外负担
- 🧪 高质量 - 97%+ 测试覆盖率
无论你是在开发单体应用还是微服务架构,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
| 特性 | 标准 Error | UnError |
|---|---|---|
| 基本错误信息 | ✅ | ✅ |
| 堆栈跟踪 | ✅ | ✅ |
| 时间戳 | ❌ | ✅ |
| 错误链 | ❌ | ✅ |
| 序列化 | ❌ | ✅ |
| 格式化输出 | ❌ | ✅ |
| 堆栈解析 | ❌ | ✅ |
| 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 在设计时充分考虑了性能:
- 轻量级:核心代码不到 500 行(未压缩,且包含注释,注释包含示例)
- 零依赖:不引入任何第三方库
- 按需计算:堆栈解析、格式化等操作只在调用时执行
- 高效序列化:使用原生 JSON 序列化,性能优异
测试覆盖
UnError 拥有完善的测试体系:
# 运行测试 npm test # 查看覆盖率 npm run test:coverage
测试统计:
- ✅ 62 个单元测试
- ✅ 97.36% 代码覆盖率
- ✅ 100% 函数覆盖率
- ✅ 87.5% 分支覆盖率
运行示例
项目包含 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内容请搜索脚本之家以前的文章或继续浏览下面的相关文章希望大家以后多多支持脚本之家!
