throw Error用法
- throw Error 是用來丟擲錯誤,便於程式處理錯誤,如果你程式裡沒有 try-catch 或者是 Promise reject,那最終這個錯誤會向上傳遞,直到被 try-catch 或者是 Promise reject 捕捉到,如果最後還沒有被捕捉到,那最終錯誤會被輸出到控制檯。
- 可以 throw 別的東西,不僅限於 Error,可以是任何資料型別,例如字串、數字、物件等。
舉例:
javascriptCopy code
try {
throw new Error("something went wrong");
} catch (e) {
console.error(e);
}
javascriptCopy code
try {
throw "something went wrong";
} catch (e) {
console.error(e);
}
下面有更好的throw Error例子
try-catch 範例
function divide(a, b) {
if (b === 0) {
throw new Error("Cannot divide by zero.");
}
return a / b;
}
try {
const result = divide(5, 0);
console.log(result);
} catch (error) {
console.error("Error:", error.message);
}
// 輸出:
// Error: Cannot divide by zero.
// 如果没有使用 try-catch 包起来,那错误将会向上传递,直到被全局的 try-catch 捕获到,或者在未被捕获到的情况下,将被显示在控制台上。
// 因此,如果没有 try-catch 或全局的 try-catch,在代码执行完后,你将会看到一条错误信息,表明程序出现了错误。
Promise reject 範例
const promise = new Promise((resolve, reject) => {
const divide = (a, b) => {
if (b === 0) {
reject(new Error("Cannot divide by zero."));
}
resolve(a / b);
};
divide(5, 0);
});
promise.then((result) => {
console.log(result);
}).catch((error) => {
console.error(error.message);
});
為什麼建議丟 Error 而不是亂丟值
- throw 後面可以接「任何值」,但強烈建議丟
Error(或其子類別)的實例。 - 理由:
Error物件會自動帶 stack trace(堆疊追蹤),debug 時能看到錯誤從哪一行冒出來;丟字串就沒有。- 很多工具 / library / log 系統預期接到的是
Error,會去讀.message、.stack,丟字串會讓它們拿到undefined。 catch端可以用instanceof判斷錯誤型別,丟字串就只能用typeof硬猜。
// ❌ 不建議:沒有 stack,難以追蹤
throw "資料庫連線失敗";
// ✅ 建議:有 message + stack + name
throw new Error("資料庫連線失敗");
常見陷阱因為什麼都能 throw,catch (e) 接到的 e 型別不保證是 Error。直接讀 e.message 在「別人丟了字串」的情況下會拿到 undefined。寫防禦性程式碼時要先判斷:catch (e) { const msg = e instanceof Error ? e.message : String(e); console.error(msg); }
Error 物件的結構
Error 是 JS 內建的建構式,產生的物件主要有三個屬性:
| 屬性 | 說明 |
|---|---|
message | 你傳進建構式的字串,描述錯誤內容 |
name | 錯誤的型別名稱,預設 "Error",子類別會是 "TypeError" 等 |
stack | 堆疊追蹤字串(非標準但所有主流引擎都有),含檔名 / 行號 |
const err = new Error("出事了");
console.log(err.message); // "出事了"
console.log(err.name); // "Error"
console.log(err.stack); // "Error: 出事了\n at ... (file.js:1:13)"
console.log(err.toString()); // "Error: 出事了" → name + ": " + message
ES2022 之後還支援 cause,用來包裝底層錯誤(保留原始錯誤鏈):
try {
await fetchUser();
} catch (e) {
throw new Error("載入使用者失敗", { cause: e });
}
內建的 Error 子類別
JS 內建幾種 Error 子類別,引擎在特定情況會自動丟出來,面試常考它們的觸發時機:
| 子類別 | 什麼時候會被丟出 |
|---|---|
TypeError | 值的型別不對,例如呼叫 null.foo()、把非函式當函式呼叫 |
RangeError | 數值超出合法範圍,例如 new Array(-1)、遞迴爆 stack |
ReferenceError | 存取一個沒宣告的變數 |
SyntaxError | 語法錯誤,例如 JSON.parse("{bad}") |
URIError | decodeURIComponent("%") 這種 URI 處理函式參數錯誤 |
EvalError | 與 eval() 相關(現代幾乎不會遇到,保留為歷史相容) |
null.foo(); // TypeError: Cannot read properties of null
notDefined; // ReferenceError: notDefined is not defined
new Array(-1); // RangeError: Invalid array length
JSON.parse("{bad}"); // SyntaxError: Unexpected token b in JSON
這些子類別都繼承自 Error,所以 e instanceof Error 對它們都成立:
try {
null.foo();
} catch (e) {
console.log(e instanceof TypeError); // true
console.log(e instanceof Error); // true
}
自訂 Error class
實務上會針對不同錯誤情境定義 自訂 Error class,讓 catch 端能用 instanceof 精準分流,而不是比對 message 字串(脆弱、易因改字而壞掉)。
class ValidationError extends Error {
constructor(message, field) {
super(message);
this.name = "ValidationError"; // 一定要設,否則 name 會是 "Error"
this.field = field; // 可以加自訂屬性
}
}
function createUser(data) {
if (!data.email) {
throw new ValidationError("email 為必填", "email");
}
}
try {
createUser({});
} catch (e) {
if (e instanceof ValidationError) {
console.error(`欄位 ${e.field} 驗證失敗:${e.message}`);
} else {
throw e; // 不是我認識的錯誤,繼續往上丟(不要吞掉)
}
}
重點
super(message)一定要呼叫,才能正確設定message與stack。- 記得手動設
this.name,預設會是"Error",log 出來會看不出是哪種錯。- TypeScript 中繼承
Error時,若 target 設為 ES5,instanceof可能失效,需要在 constructor 加Object.setPrototypeOf(this, ValidationError.prototype)。
TypeScript 版本(順便標型別):
class ValidationError extends Error {
constructor(
message: string,
public readonly field: string,
) {
super(message);
this.name = "ValidationError";
}
}
try / catch / finally
完整的錯誤處理結構有三塊,catch 與 finally 至少要有一個:
try {
// 可能丟錯的程式碼
doSomething();
} catch (error) {
// 只有 try 區塊丟錯時才會進來
handle(error);
} finally {
// 不管有沒有錯、有沒有 return,都一定會執行
cleanup();
}
try:放可能出錯的程式碼。catch (error):捕捉錯誤。ES2019 起參數可省略(catch {}),用在你不在乎錯誤內容時。finally:無論成功、失敗、甚至 try/catch 裡有return,都會執行,常用來釋放資源(關連線、清 timer)。
finally 的陷阱如果 finally 裡有 return,它會覆蓋掉 try / catch 裡的 return,甚至吞掉正在往上丟的錯誤:function f() { try { return 1; } finally { return 2; // 最終回傳 2,try 的 1 被蓋掉! } } f(); // 2所以
finally裡盡量只做清理,不要寫return/throw。
async / Promise 的錯誤處理
非同步是面試重災區,重點在「錯誤怎麼傳遞、怎麼接」。
Promise:用 .catch 或 reject
fetchData()
.then((data) => process(data))
.catch((err) => console.error(err)); // 鏈上任一環 reject 或 throw 都會掉到這
.then 的 callback 裡 throw 等同於回傳一個 rejected Promise,會被後面的 .catch 接住。
async/await:用 try-catch
async function load() {
try {
const res = await fetch("/api/user");
if (!res.ok) {
throw new Error(`HTTP ${res.status}`); // fetch 本身不會因 4xx/5xx 而 reject,要自己丟
}
return await res.json();
} catch (err) {
console.error("載入失敗:", err);
throw err; // 視情況決定要吞還是往上丟
}
}
async 常見陷阱
- 忘記
await:沒 await 的 async function 回傳的是 Promise,裡面 throw 的錯不會被外層 try-catch 接到,會變成 unhandled rejection。try { load(); // ❌ 少了 await,這裡的 try-catch 接不到 load 內部的錯 } catch (e) { /* 永遠不會進來 */ }fetch不會因 HTTP 錯誤碼 reject:只有網路層失敗(斷線、CORS)才 reject,4xx/5xx 要自己檢查res.ok再 throw。- forEach 裡的 async:
array.forEach(async () => { await ... })不會等待,錯誤也接不到,要改用for...of+ await 或Promise.all。
多個 Promise 的錯誤
| 方法 | 行為 |
|---|---|
Promise.all | 任一個 reject 就整體 reject(fail-fast) |
Promise.allSettled | 全部跑完,回傳每個的成功 / 失敗結果,不會 reject |
Promise.race | 第一個 settle(成功或失敗)的結果決定整體 |
Promise.any | 第一個成功的決定整體;全部失敗才 reject(AggregateError) |
最佳實務
- 不要吞錯(swallow error):空的
catch {}或只catch (e) {}什麼都不做,會讓 bug 無聲消失,是最該避免的反模式。// ❌ 最糟:錯誤被吞,之後完全查不到原因 try { risky(); } catch (e) {} // ✅ 至少 log,並決定要不要往上丟 try { risky(); } catch (e) { logger.error(e); throw e; // 不確定能處理就往上丟,別自作主張 } - 只 catch 你能處理的錯:認不得的錯誤就
throw e繼續往上拋,交給有能力處理的層級。 - 用
instanceof分流,別比對 message 字串:message 是給人看的,隨時會改;型別才是穩定的判斷依據。 - 丟有意義的 Error:message 寫清楚情境,必要時用自訂 class +
cause保留錯誤鏈。 - 資源清理放 finally:確保連線 / 檔案 / timer 一定被釋放。
- 非同步一律 await + try-catch,或 Promise 一定接
.catch,避免 unhandled rejection。