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

 更新时间:2025年11月29日 10:19:09   作者:会功夫的李白  
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 的诸多痛点:

  • 🎯 简洁的 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

特性标准 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

测试统计:

  • 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内容请搜索脚本之家以前的文章或继续浏览下面的相关文章希望大家以后多多支持脚本之家!

相关文章

  • js实现登录弹框

    js实现登录弹框

    这篇文章主要为大家详细介绍了js实现登录弹框,文中示例代码介绍的非常详细,具有一定的参考价值,感兴趣的小伙伴们可以参考一下
    2022-08-08
  • 嵌入式iframe子页面与父页面js通信的方法

    嵌入式iframe子页面与父页面js通信的方法

    这篇文章主要介绍了嵌入式iframe子页面与父页面js通信的方法,实例分析了嵌入式iframe子页面与父页面js通信的常用技巧,非常具有实用价值,需要的朋友可以参考下
    2015-01-01
  • JS实现给对象动态添加属性的方法

    JS实现给对象动态添加属性的方法

    这篇文章主要介绍了JS实现给对象动态添加属性的方法,涉及JS属性的遍历、动态赋值及eval方法的简单使用技巧,需要的朋友可以参考下
    2017-01-01
  • Js+Jq获取URL参数的集中方法示例代码

    Js+Jq获取URL参数的集中方法示例代码

    这篇文章主要介绍了Js+Jq获取URL参数的集中方法,需要的朋友可以参考下
    2014-05-05
  • js实现调用网络摄像头及常见错误处理

    js实现调用网络摄像头及常见错误处理

    这篇文章主要介绍了js实现调用网络摄像头及常见错误处理,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友们下面随着小编来一起学习学习吧
    2021-03-03
  • Add a Table to a Word Document

    Add a Table to a Word Document

    Add a Table to a Word Document...
    2007-06-06
  • jstree的简单实例

    jstree的简单实例

    最近使用到了jstree,感觉是一款灵活的、可多项定制的tree插件。下面通过本文给大家详细介绍下jstree的简单实例,需要的朋友可以参考下
    2016-12-12
  • layui中select,radio设置不生效的解决方法

    layui中select,radio设置不生效的解决方法

    今天小编就为大家分享一篇layui中select,radio设置不生效的解决方法,具有很好的参考价值,希望对大家有所帮助。一起跟随小编过来看看吧
    2019-09-09
  • 解决Layui选择全部,换页checkbox复选框重新勾选的问题方法

    解决Layui选择全部,换页checkbox复选框重新勾选的问题方法

    今天小编就为大家分享一篇解决Layui选择全部,换页checkbox复选框重新勾选的问题方法,具有很好的参考价值,希望对大家有所帮助。一起跟随小编过来看看吧
    2018-08-08
  • JavaScript工具库之Lodash详解

    JavaScript工具库之Lodash详解

    这篇文章主要介绍了JavaScript工具库之Lodash详解,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,,需要的朋友可以参考下
    2019-06-06

最新评论