throw
try
陳述式
try
區塊catch
子句finally
子句Error
及其子類別
Error
Error
的內建子類別Error
的子類別error.cause
鏈結錯誤 [ES2022].cause
的替代方案:自訂錯誤類別本章節涵蓋 JavaScript 如何處理例外。
為什麼 JavaScript 沒有更常拋出例外?
JavaScript 直到 ES3 才支援例外。這說明了為什麼語言及其標準函式庫很少使用它們。
考慮以下程式碼。它會將儲存在檔案中的個人資料讀取到陣列中,其中包含 Profile
類別的執行個體
function readProfiles(filePaths) {
const profiles = [];
for (const filePath of filePaths) {
try {
const profile = readOneProfile(filePath);
.push(profile);
profilescatch (err) { // (A)
} console.log('Error in: '+filePath, err);
}
}
}function readOneProfile(filePath) {
const profile = new Profile();
const file = openFile(filePath);
// ··· (Read the data in `file` into `profile`)
return profile;
}function openFile(filePath) {
if (!fs.existsSync(filePath)) {
throw new Error('Could not find file '+filePath); // (B)
}// ··· (Open the file whose path is `filePath`)
}
讓我們檢查 B 行中發生了什麼事:發生錯誤,但處理問題的最佳位置不是目前的位置,而是 A 行。在那裡,我們可以跳過目前的檔案並繼續處理下一個檔案。
因此
throw
陳述式來指出發生問題。try-catch
陳述式來處理問題。當我們拋出時,以下建構會處於作用中
readProfiles(···)
for (const filePath of filePaths)
try
readOneProfile(···)
openFile(···)
if (!fs.existsSync(filePath))
throw
throw
會逐一退出巢狀建構,直到它遇到 try
陳述式。執行會繼續在該 try
陳述式的 catch
子句中。
throw
以下是 throw
語句的語法
throw «value»;
在 JavaScript 中,可以擲出任何值。然而,最好使用 Error
或其子類別的實例,因為它們支援其他功能,例如堆疊追蹤和錯誤鏈結(請參閱 §24.4 “Error
及其子類別”)。
這讓我們有以下選項
直接使用 Error
類別。在 JavaScript 中,這比在更靜態的語言中限制較少,因為我們可以將自己的屬性新增至實例
const err = new Error('Could not find the file');
.filePath = filePath;
errthrow err;
使用 Error
的子類別 之一。
建立 Error
的子類別(更多詳細資訊說明於 稍後)
class MyError extends Error {
}function func() {
throw new MyError('Problem!');
}.throws(
assert=> func(),
() ; MyError)
try
語句try
語句的最大版本如下所示
try {
«try_statements»catch (error) {
}
«catch_statements»finally {
}
«finally_statements» }
我們可以將這些子句組合如下
try-catch
try-finally
try-catch-finally
try
區塊try
區塊可以被視為語句的主體。這是我們執行一般程式碼的地方。
catch
子句如果例外狀況到達 try
區塊,則會將其指定給 catch
子句的參數,並執行該子句中的程式碼。接下來,執行通常會在 try
語句之後繼續。如果發生下列情況,這可能會改變
catch
區塊內有 return
、break
或 throw
。finally
子句(它總是在 try
語句結束前執行)。以下程式碼示範 A 行中擲出的值確實會在 B 行中被捕獲。
const errorObject = new Error();
function func() {
throw errorObject; // (A)
}
try {
func();
catch (err) { // (B)
} .equal(err, errorObject);
assert }
catch
繫結 [ES2019]如果我們對擲出的值不感興趣,則可以省略 catch
參數
try {
// ···
catch {
} // ···
}
這偶爾可能會很有用。例如,Node.js 有 API 函式 assert.throws(func)
,它會檢查 func
內部是否擲出錯誤。它可以實作如下。
function throws(func) {
try {
func();
catch {
} return; // everything OK
}throw new Error('Function didn’t throw an exception!');
}
然而,此函式的更完整實作會有 catch
參數,並會檢查其類型是否如預期般,例如。
finally
子句finally
子句內的程式碼總是在 try
語句的結尾執行,無論在 try
區塊或 catch
子句中發生什麼事。
讓我們看看 finally
的常見使用案例:我們已建立一個資源,並希望在我們使用完畢後始終銷毀它,無論在使用過程中發生什麼事。我們會實作如下
const resource = createResource();
try {
// Work with `resource`. Errors may be thrown.
finally {
} .destroy();
resource }
finally
總是會執行即使發生錯誤(A 行),finally
子句也會執行
let finallyWasExecuted = false;
.throws(
assert=> {
() try {
throw new Error(); // (A)
finally {
} = true;
finallyWasExecuted
},
}Error
;
).equal(finallyWasExecuted, true); assert
即使有 return
陳述式(A 行)
let finallyWasExecuted = false;
function func() {
try {
return; // (A)
finally {
} = true;
finallyWasExecuted
}
}func();
.equal(finallyWasExecuted, true); assert
Error
及其子類別Error
是所有內建錯誤類別的共同父類別。
Error
以下是 Error
的實例屬性和建構函式的樣子
class Error {
// Instance properties
: string;
message?: any; // ES2022
cause: string; // non-standard but widely supported
stack
constructor(
: string = '',
message?: ErrorOptions // ES2022
options;
)
}interface ErrorOptions {
?: any; // ES2022
cause }
建構函式有兩個參數
message
指定錯誤訊息。options
在 ECMAScript 2022 中引入。它包含一個物件,其中目前支援一個屬性
.cause
指定導致目前錯誤的例外狀況(如果有)。下一個子節之後的子節會更詳細地說明實例屬性 .message
、.cause
和 .stack
。
Error.prototype.name
每個內建錯誤類別 E
都有一個屬性 E.prototype.name
> Error.prototype.name'Error'
> RangeError.prototype.name'RangeError'
因此,有兩種方法可以取得內建錯誤物件的類別名稱
> new RangeError().name'RangeError'
> new RangeError().constructor.name'RangeError'
.message
.message
僅包含錯誤訊息
const err = new Error('Hello!');
.equal(String(err), 'Error: Hello!');
assert.equal(err.message, 'Hello!'); assert
如果我們省略訊息,則會使用空字串作為預設值(繼承自 Error.prototype.message
)
如果我們省略訊息,它會是空字串
.equal(new Error().message, ''); assert
.stack
實例屬性 .stack
不是 ECMAScript 功能,但它受到 JavaScript 引擎的廣泛支援。它通常是字串,但其確切結構未標準化,且因引擎而異。
以下是它在 JavaScript 引擎 V8 上的樣子
const err = new Error('Hello!');
.equal(
assert.stack,
err`
Error: Hello!
at file://ch_exception-handling.mjs:1:13
`.trim());
.cause
[ES2022]實例屬性 .cause
是透過 new Error()
第二個參數中的選項物件建立的。它指定導致目前錯誤的其他錯誤。
const err = new Error('msg', {cause: 'the cause'});
.equal(err.cause, 'the cause'); assert
有關如何使用此屬性的資訊,請參閱 §24.5「串連錯誤」。
Error
的內建子類別Error
有以下子類別 - 引用 ECMAScript 規範
AggregateError
[ES2021] 一次表示多個錯誤。在標準函式庫中,只有 Promise.any()
使用它。RangeError
指出不在允許值集合或範圍內的數值。ReferenceError
指出已偵測到無效的參考值。SyntaxError
指出發生了剖析錯誤。TypeError
用於指出不成功的操作,當其他 NativeError 物件都不適合作為失敗原因的適當指示時。URIError
指出其中一個全域 URI 處理函式以與其定義不相容的方式使用。Error
自 ECMAScript 2022 以來,Error
建構函式接受兩個參數(請參閱前一個子節)。因此,當我們對其進行子類化時,我們有兩種選擇:我們可以在我們的子類別中省略建構函式,或者我們可以像這樣呼叫 super()
class MyCustomError extends Error {
constructor(message, options) {
super(message, options);
// ···
} }
有時,我們會捕捉在更深層巢狀函式呼叫期間引發的錯誤,並希望附加更多資訊給它
function readFiles(filePaths) {
return filePaths.map(
=> {
(filePath) try {
const text = readText(filePath);
const json = JSON.parse(text);
return processJson(json);
catch (error) {
} // (A)
};
}) }
try
子句內的陳述式可能會引發各種錯誤。在多數情況下,錯誤不會知道導致它的檔案路徑。這就是我們希望在 A 行附加該資訊的原因。
error.cause
鏈結錯誤 [ES2022]自 ECMAScript 2022 以來,new Error()
讓我們可以指定導致它的原因
function readFiles(filePaths) {
return filePaths.map(
=> {
(filePath) try {
// ···
catch (error) {
} throw new Error(
`While processing ${filePath}`,
cause: error}
{;
)
};
}) }
.cause
的替代方案:自訂錯誤類別下列自訂錯誤類別支援鏈結。它與 .cause
向前相容。
/**
* An error class that supports error chaining.
* If there is built-in support for .cause, it uses it.
* Otherwise, it creates this property itself.
*
* @see https://github.com/tc39/proposal-error-cause
*/
class CausedError extends Error {
constructor(message, options) {
super(message, options);
if (
isObject(options) && 'cause' in options)
(&& !('cause' in this)
) {// .cause was specified but the superconstructor
// did not create an instance property.
const cause = options.cause;
this.cause = cause;
if ('stack' in cause) {
this.stack = this.stack + '\nCAUSE: ' + cause.stack;
}
}
}
}
function isObject(value) {
return value !== null && typeof value === 'object';
}
練習:例外處理
exercises/exception-handling/call_function_test.mjs
測驗
請參閱 測驗應用程式。