JavaScript利用Error.cause進(jìn)行錯(cuò)誤調(diào)試的方法詳解
1. 前言
在 JavaScript 中,拋出錯(cuò)誤很容易,但追溯原因卻有些麻煩,這就是 cause屬性的用武之地。
這是我們傳統(tǒng)的處理錯(cuò)誤的做法:
try {
JSON.parse("{ bad json }");
} catch (err) {
throw new Error("Something went wrong: " + err.message);
}
雖然包裝了錯(cuò)誤,但已經(jīng)丟失了原始的堆棧信息和錯(cuò)誤類型。
當(dāng)問題發(fā)生時(shí),你只能看到最頂層的錯(cuò)誤信息,卻不知道根本原因是什么。
你好,我是冴羽。前端資訊、前端干貨,歡迎關(guān)注公眾號(hào):冴羽
2. 引入 Error.cause
ES2022 引入了 Error.cause 屬性,可以保留原始錯(cuò)誤信息:
try {
try {
JSON.parse("{ bad json }");
} catch (err) {
throw new Error("Something went wrong", { cause: err });
}
} catch (err) {
console.error(err.stack);
console.error("Caused by:", err.cause.stack);
}
此時(shí)你可以看到完整的錯(cuò)誤鏈:
Error: Something went wrong
at ...
Caused by: SyntaxError: Unexpected token b in JSON at position 2
at JSON.parse (<anonymous>)
at ...
現(xiàn)在,你既保留了原始錯(cuò)誤,又能提供清晰的頂層錯(cuò)誤信息。
3. 實(shí)際應(yīng)用示例
讓我們看一個(gè)更實(shí)際的例子:
function fetchUserData() {
try {
JSON.parse("{ broken: true }"); // ← 這里會(huì)失敗
} catch (parseError) {
throw new Error("Failed to fetch user data", { cause: parseError });
}
}
try {
fetchUserData();
} catch (err) {
console.error(err.message); // "Failed to fetch user data"
console.error(err.cause); // [SyntaxError: Unexpected token b in JSON]
console.error(err.cause instanceof SyntaxError); // true
}
可以看到代碼非常清晰直觀。
而且 cause 屬性被定義為不可枚舉,因此它不會(huì)污染日志或 for...in 循環(huán),除非你顯式訪問它。
4. 自定義錯(cuò)誤類
你可以在自定義錯(cuò)誤類中使用 cause 屬性:
class DatabaseError extends Error {
constructor(message, { cause } = {}) {
super(message, { cause });
this.name = "DatabaseError";
}
}
如果你的運(yùn)行環(huán)境是 ES2022+,這已經(jīng)足夠了:super(message, { cause }) 會(huì)自動(dòng)處理一切。
對(duì)于 TypeScript 用戶,確保 tsconfig.json 配置了:
{
"compilerOptions": {
"target": "es2022",
"lib": ["es2022"]
}
}
否則,在將 { cause } 傳遞給 Error 構(gòu)造函數(shù)時(shí)可能會(huì)看到類型錯(cuò)誤。
5. 更好的測(cè)試斷言
假設(shè)你的服務(wù)拋出了一個(gè) UserCreationError,這是由一個(gè) ValidationError 引發(fā)的。
你可以這樣寫斷言:
expect(err.cause).toBeInstanceOf(ValidationError);
這樣測(cè)試會(huì)更清晰、更健壯。
6. 注意事項(xiàng)
默認(rèn)情況下,console.error(err) 只會(huì)打印頂層錯(cuò)誤。cause鏈不會(huì)自動(dòng)顯示,因此需要手動(dòng)打?。?/p>
console.error(err);
console.error("Caused by:", err.cause);
盡管 cause 很好,但也不要濫用。每個(gè)小錯(cuò)誤都包裝可能更亂,因此只在真正需要上下文的時(shí)候使用。
7. 遞歸打印完整錯(cuò)誤鏈
這是一個(gè)安全遍歷錯(cuò)誤鏈的工具函數(shù):
function logErrorChain(err, level = 0) {
if (!err) return;
console.error(" ".repeat(level * 2) + `${err.name}: ${err.message}`);
if (err.cause instanceof Error) {
logErrorChain(err.cause, level + 1);
} else if (err.cause) {
console.error(" ".repeat((level + 1) * 2) + String(err.cause));
}
}
如果需要完整堆棧信息:
function logFullErrorChain(err) {
let current = err;
while (current) {
console.error(current.stack);
current = current.cause instanceof Error ? current.cause : null;
}
}
對(duì)于結(jié)構(gòu)復(fù)雜、可能在不同層級(jí)出現(xiàn)多種故障的系統(tǒng)來說,這非常有用。
8. 跨層錯(cuò)誤鏈?zhǔn)纠?/h2>
假設(shè)調(diào)用流程如下:
- 數(shù)據(jù)庫連接失敗,拋出
ConnectionTimeoutError - 捕獲后包裝成
DatabaseError - 再次捕獲并包裝成
ServiceUnavailableError
class ConnectionTimeoutError extends Error {}
class DatabaseError extends Error {}
class ServiceUnavailableError extends Error {}
try {
try {
try {
throw new ConnectionTimeoutError("DB connection timed out");
} catch (networkErr) {
throw new DatabaseError("Failed to connect to database", { cause: networkErr });
}
} catch (dbErr) {
throw new ServiceUnavailableError("Unable to save user data", { cause: dbErr });
}
} catch (finalErr) {
logErrorChain(finalErr);
}
控制臺(tái)輸出:
ServiceUnavailableError: Unable to save user data
DatabaseError: Failed to connect to database
ConnectionTimeoutError: DB connection timed out
可以看到,錯(cuò)誤鏈提供了一個(gè)清晰的視圖,告訴你發(fā)生了什么以及在哪里發(fā)生的。
9. 支持度
.cause 參數(shù)在所有現(xiàn)代環(huán)境中都支持:
- Chrome 93+、Firefox 91+、Safari 15+、Edge 93+
- Node.js 16.9+
- Bun 和 Deno(當(dāng)前版本)
需要注意的是,開發(fā)者工具可能不會(huì)自動(dòng)顯示 cause。
所以需要顯式記錄它(console.error('Caused by:', err.cause))。
還要注意:如果使用 Babel 或 TypeScript 進(jìn)行轉(zhuǎn)譯,此功能不會(huì)被 polyfill。
10. 異步操作中的錯(cuò)誤處理
Error.cause 同樣適用于異步操作。結(jié)合 async/await 可以這樣使用:
async function fetchData() {
try {
const response = await fetch("/api/data");
if (!response.ok) {
throw new Error(`HTTP error! Status: ${response.status}`);
}
return await response.json();
} catch (error) {
console.error("數(shù)據(jù)獲取失敗:", error);
throw new Error("Failed to fetch data", { cause: error });
}
}
這種方式讓異步代碼的錯(cuò)誤處理邏輯看起來與同步代碼無異,大大提升了可讀性和可維護(hù)性。
11. 總結(jié)
總結(jié)一下,現(xiàn)代錯(cuò)誤鏈處理的最佳實(shí)踐:
- 使用
new Error(message, { cause })保留上下文 - 適用于內(nèi)置錯(cuò)誤類和自定義錯(cuò)誤類
- 所有現(xiàn)代運(yùn)行時(shí)環(huán)境都支持(瀏覽器、Node.js、Deno、Bun)
- 可以改善日志、調(diào)試和測(cè)試斷言
- 注意 TypeScript:設(shè)置
"target": "es2022"和"lib": ["es2022"] - 注意記錄
err.cause或手動(dòng)遍歷錯(cuò)誤鏈
從而實(shí)現(xiàn)更清晰的堆棧跟蹤、更好的上下文、更愉快的調(diào)試體驗(yàn)。
Error.cause 就是你錯(cuò)誤處理中缺少的那一環(huán)。
到此這篇關(guān)于JavaScript利用Error.cause進(jìn)行錯(cuò)誤調(diào)試的方法詳解的文章就介紹到這了,更多相關(guān)JavaScript錯(cuò)誤調(diào)試內(nèi)容請(qǐng)搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
相關(guān)文章
JS面向?qū)ο缶幊袒A(chǔ)篇(二) 封裝操作實(shí)例詳解
這篇文章主要介紹了JS面向?qū)ο缶幊谭庋b操作,結(jié)合實(shí)例形式詳細(xì)分析了JS面向?qū)ο蠓庋b操作的相關(guān)概念、原理、使用方法與操作注意事項(xiàng),需要的朋友可以參考下2020-03-03
說明你的Javascript技術(shù)很爛的五個(gè)原因
Javascript在互聯(lián)網(wǎng)上名聲很臭,但你又很難再找到一個(gè)像它這樣如此動(dòng)態(tài)、如此被廣泛使用、如此根植于我們的生活中的另外一種語言。2011-04-04
構(gòu)建一個(gè)JavaScript插件系統(tǒng)
這篇文章主要介紹了如何構(gòu)建一個(gè)JavaScript插件系統(tǒng),幫助大家更好的理解和使用JavaScript,感興趣的朋友可以了解下2020-10-10
寫入cookie的JavaScript代碼庫 cookieLibrary.js
cookieLibrary.js 寫入cookie的JavaScript代碼庫,需要的朋友可以參考下。2009-10-10
js性能優(yōu)化之?dāng)?shù)組模式實(shí)例詳解
這篇文章主要為大家介紹了js性能優(yōu)化之?dāng)?shù)組模式實(shí)例詳解,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進(jìn)步,早日升職加薪2023-10-10

