UnError如何讓JavaScript錯誤處理更優(yōu)雅詳解
一個輕量級、類型安全的統(tǒng)一錯誤處理庫
痛點:JavaScript 錯誤處理的困境
作為一名 JavaScript/TypeScript 開發(fā)者,你一定遇到過這些令人頭疼的問題:
問題 1:錯誤信息貧乏
throw new Error('User not found');
這個錯誤告訴了我們什么?只有一句話。我們不知道:
- 什么時候發(fā)生的?
- 在哪個模塊?
- 有沒有更詳細的上下文?
- 根本原因是什么?
問題 2:錯誤鏈追蹤困難
try {
await database.connect();
} catch (err) {
// 原始錯誤信息丟失了!
throw new Error('Failed to fetch user');
}
當錯誤在多層調(diào)用中傳遞時,我們往往會丟失原始的錯誤信息。
問題 3:錯誤無法序列化
const error = new Error('Something went wrong');
const json = JSON.stringify(error);
console.log(json); // "{}" - 空對象!
標準的 Error 對象無法被 JSON 序列化,這在分布式系統(tǒng)中是個大問題。
問題 4:微服務(wù)間錯誤傳遞麻煩
在微服務(wù)架構(gòu)中,錯誤需要在不同服務(wù)之間傳遞。但標準 Error 對象:
- 無法序列化
- 無法保留完整的錯誤鏈
- 無法攜帶額外的上下文信息
解決方案:UnError
UnError 是一個現(xiàn)代化的 JavaScript 錯誤處理庫,它解決了標準 Error 的諸多痛點:
- ?? 簡潔的 API - 學(xué)習(xí)成本低,上手快
- ?? 完整的錯誤鏈 - 追蹤錯誤的來龍去脈
- ?? 可序列化 - 完美支持分布式系統(tǒng)
- ?? 格式化輸出 - 人類可讀的錯誤描述
- ?? 類型安全 - 完整的 TypeScript 支持
- ?? 零依賴 - 輕量級,不引入額外負擔(dān)
- ?? 高質(zhì)量 - 97%+ 測試覆蓋率
無論你是在開發(fā)單體應(yīng)用還是微服務(wù)架構(gòu),UnError 都能讓你的錯誤處理更加優(yōu)雅和高效。
快速開始
安裝
npm install unerror
基本用法
import { UnError, createError, error } from 'unerror';
// 創(chuàng)建錯誤
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 最強大的功能之一就是錯誤鏈追蹤。它可以完整記錄錯誤的傳播路徑:
// 第一層:數(shù)據(jù)庫錯誤
const dbError = new Error('Connection timeout');
// 第二層:查詢錯誤
const queryError = new UnError('Query execution failed', dbError);
// 第三層:服務(wù)錯誤
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,這對分布式系統(tǒng)至關(guān)重要:
// 服務(wù) A:創(chuàng)建錯誤并序列化
const error = new UnError('Database connection failed');
const json = JSON.stringify(error.toJSON());
// 通過網(wǎng)絡(luò)傳輸?shù)椒?wù) B...
await fetch('/api/log', {
method: 'POST',
body: json
});
// 服務(wù) 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 可以解析堆棧跟蹤,提取結(jié)構(gòu)化信息:
const error = new UnError('Something went wrong');
const frames = error.parseStack();
frames.forEach(frame => {
console.log(`函數(shù): ${frame.functionName}`);
console.log(`文件: ${frame.fileName}`);
console.log(`行號: ${frame.lineNumber}`);
console.log(`列號: ${frame.columnNumber}`);
});
5. 自定義錯誤類
使用工廠函數(shù)快速創(chuàng)建自定義錯誤類:
import { createErrorClass } from 'unerror';
// 創(chuàng)建自定義錯誤類
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());
實戰(zhàn)場景
場景 1:Express API 錯誤處理
import express from 'express';
import { UnError } from 'unerror';
const app = express();
// 業(yè)務(wù)邏輯
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:錯誤監(jiān)控和分析
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(`發(fā)生位置: ${topFrame.fileName}:${topFrame.lineNumber}`);
console.log(`函數(shù): ${topFrame.functionName || '(anonymous)'}`);
}
console.log('\n錯誤鏈:');
chain.forEach((err, index) => {
console.log(` ${index + 1}. ${err.message}`);
});
// 發(fā)送到監(jiān)控服務(wù)
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() {
// 按小時分組統(tǒng)計
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() {
// 統(tǒng)計最常見的錯誤
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 支持 | 基礎(chǔ) | 完整 |
vs 手動實現(xiàn)
// ? 手動實現(xiàn) - 需要大量代碼
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() {
// 需要手動實現(xiàn)序列化
return {
name: this.name,
message: this.message,
timestamp: this.timestamp,
stack: this.stack,
cause: this.cause ? /* 遞歸序列化 */ : undefined
};
}
format() {
// 需要手動實現(xiàn)格式化
// ...
}
// 還需要實現(xiàn) fromJSON、getErrorChain、parseStack 等方法
}
// ? UnError - 開箱即用
import { UnError } from 'unerror';
const error = new UnError('Something went wrong');
// 所有功能都已內(nèi)置
技術(shù)細節(jié)
類型安全
UnError 使用 TypeScript 編寫,提供完整的類型定義:
import type {
UnError,
SerializedError,
FormatOptions,
StackFrame
} from 'unerror';
// 類型守衛(wèi)
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();
性能優(yōu)化
UnError 在設(shè)計時充分考慮了性能:
- 輕量級:核心代碼不到 500 行(未壓縮,且包含注釋,注釋包含示例)
- 零依賴:不引入任何第三方庫
- 按需計算:堆棧解析、格式化等操作只在調(diào)用時執(zhí)行
- 高效序列化:使用原生 JSON 序列化,性能優(yōu)異
測試覆蓋
UnError 擁有完善的測試體系:
# 運行測試 npm test # 查看覆蓋率 npm run test:coverage
測試統(tǒng)計:
- ? 62 個單元測試
- ? 97.36% 代碼覆蓋率
- ? 100% 函數(shù)覆蓋率
- ? 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 # 原型鏈演示
總結(jié)
到此這篇關(guān)于UnError如何讓JavaScript錯誤處理更優(yōu)雅的文章就介紹到這了,更多相關(guān)JS錯誤處理UnError內(nèi)容請搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
相關(guān)文章
Javascript中判斷一個值是否為undefined的方法詳解
這篇文章給大家詳細介紹了在Javascript中如何判斷一個值是否為undefined,對大家的日常工作和學(xué)習(xí)很有幫助,下面來一起看看吧。2016-09-09
微信小程序五子棋游戲AI實現(xiàn)方法【附demo源碼下載】
這篇文章主要介紹了微信小程序五子棋游戲AI實現(xiàn)方法,結(jié)合實例形式分析了五子棋游戲中人機對戰(zhàn)的AI原理及相關(guān)實現(xiàn)技巧,并附帶demo源碼供讀者下載參考,需要的朋友可以參考下2019-02-02
TypeScript 中的 .d.ts 文件詳解(加強類型支持提升開發(fā)效率)
.d.ts 文件在 TypeScript 開發(fā)中扮演著非常重要的角色,它們讓我們能夠享受到 TypeScript 強大的類型系統(tǒng)帶來的優(yōu)勢,提高代碼質(zhì)量和開發(fā)效率,接下來,我們將深入探討如何為 JavaScript 庫和自定義模塊創(chuàng)建 .d.ts 文件,以及一些最佳實踐和注意事項,一起看看吧2023-09-09
JS 動態(tài)判斷PC和手機瀏覽器實現(xiàn)代碼
這篇文章主要介紹了JS 動態(tài)判斷PC和手機瀏覽器實現(xiàn)代碼的相關(guān)資料,需要的朋友可以參考下2016-09-09
layui 數(shù)據(jù)表格 根據(jù)值(1=業(yè)務(wù),2=機構(gòu))顯示中文名稱示例
今天小編就為大家分享一篇layui 數(shù)據(jù)表格 根據(jù)值(1=業(yè)務(wù),2=機構(gòu))顯示中文名稱示例,具有很好的參考價值,希望對大家有所幫助。一起跟隨小編過來看看吧2019-10-10
JavaScript高級程序設(shè)計 事件學(xué)習(xí)筆記
JavaScript高級程序設(shè)計 事件學(xué)習(xí)筆記,需要的朋友可以參考下。2011-09-09

