W3Cschool
恭喜您成為首批注冊用戶
獲得88經(jīng)驗值獎勵
當(dāng)我們在開發(fā)某些東西時,經(jīng)常會需要我們自己的 error 類來反映在我們的任務(wù)中可能出錯的特定任務(wù)。對于網(wǎng)絡(luò)操作中的 error,我們需要 ?HttpError
?,對于數(shù)據(jù)庫操作中的 error,我們需要 ?DbError
?,對于搜索操作中的 error,我們需要 ?NotFoundError
?,等等。
我們自定義的 error 應(yīng)該支持基本的 error 的屬性,例如 message
,name
,并且最好還有 stack
。但是它們也可能會有其他屬于它們自己的屬性,例如,HttpError
對象可能會有一個 statusCode
屬性,屬性值可能為 404
、403
或 500
等。
JavaScript 允許將 throw
與任何參數(shù)一起使用,所以從技術(shù)上講,我們自定義的 error 不需要從 Error
中繼承。但是,如果我們繼承,那么就可以使用 obj instanceof Error
來識別 error 對象。因此,最好繼承它。
隨著開發(fā)的應(yīng)用程序的增長,我們自己的 error 自然會形成形成一個層次結(jié)構(gòu)(hierarchy)。例如,HttpTimeoutError
可能繼承自 HttpError
,等等。
例如,讓我們考慮一個函數(shù) readUser(json)
,該函數(shù)應(yīng)該讀取帶有用戶數(shù)據(jù)的 JSON。
這里是一個可用的 json
的例子:
let json = `{ "name": "John", "age": 30 }`;
在函數(shù)內(nèi)部,我們將使用 JSON.parse
。如果它接收到格式不正確的 json
,就會拋出 SyntaxError
。但是,即使 json
在語法上是正確的,也不意味著該數(shù)據(jù)是有效的用戶數(shù)據(jù),對吧?因為它可能丟失了某些必要的數(shù)據(jù)。例如,對用戶來說,必不可少的是 name
和 age
屬性。
我們的函數(shù) readUser(json)
不僅會讀取 JSON,還會檢查(“驗證”)數(shù)據(jù)。如果沒有所必須的字段,或者(字段的)格式錯誤,那么就會出現(xiàn)一個 error。并且這些并不是 SyntaxError
,因為這些數(shù)據(jù)在語法上是正確的,這些是另一種錯誤。我們稱之為 ValidationError
,并為之創(chuàng)建一個類。這種類型的錯誤也應(yīng)該包含有關(guān)違規(guī)字段的信息。
我們的 ValidationError
類應(yīng)該繼承自 Error
類。
Error
類是內(nèi)建的,但這是其近似代碼,所以我們可以了解我們要擴展的內(nèi)容:
// JavaScript 自身定義的內(nèi)建的 Error 類的“偽代碼”
class Error {
constructor(message) {
this.message = message;
this.name = "Error"; // (不同的內(nèi)建 error 類有不同的名字)
this.stack = <call stack>; // 非標(biāo)準(zhǔn)的,但大多數(shù)環(huán)境都支持它
}
}
現(xiàn)在讓我們從其中繼承 ValidationError
,并嘗試進行運行:
class ValidationError extends Error {
constructor(message) {
super(message); // (1)
this.name = "ValidationError"; // (2)
}
}
function test() {
throw new ValidationError("Whoops!");
}
try {
test();
} catch(err) {
alert(err.message); // Whoops!
alert(err.name); // ValidationError
alert(err.stack); // 一個嵌套調(diào)用的列表,每個調(diào)用都有對應(yīng)的行號
}
請注意:在 (1)
行中我們調(diào)用了父類的 constructor。JavaScript 要求我們在子類的 constructor 中調(diào)用 super
,所以這是必須的。父類的 constructor 設(shè)置了 message
屬性。
父類的 constructor 還將 name
屬性的值設(shè)置為了 "Error"
,所以在 (2)
行中,我們將其重置為了右邊的值。
讓我們嘗試在 readUser(json)
中使用它吧:
class ValidationError extends Error {
constructor(message) {
super(message);
this.name = "ValidationError";
}
}
// 用法
function readUser(json) {
let user = JSON.parse(json);
if (!user.age) {
throw new ValidationError("No field: age");
}
if (!user.name) {
throw new ValidationError("No field: name");
}
return user;
}
// try..catch 的工作示例
try {
let user = readUser('{ "age": 25 }');
} catch (err) {
if (err instanceof ValidationError) {
alert("Invalid data: " + err.message); // Invalid data: No field: name
} else if (err instanceof SyntaxError) { // (*)
alert("JSON Syntax Error: " + err.message);
} else {
throw err; // 未知的 error,再次拋出 (**)
}
}
上面代碼中的 try..catch
塊既處理我們的 ValidationError
又處理來自 JSON.parse
的內(nèi)建 SyntaxError
。
請看一下我們是如何使用 instanceof
來檢查 (*)
行中的特定錯誤類型的。
我們也可以看看 err.name
,像這樣:
// ...
// instead of (err instanceof SyntaxError)
} else if (err.name == "SyntaxError") { // (*)
// ...
使用 instanceof
的版本要好得多,因為將來我們會對 ValidationError
進行擴展,創(chuàng)建它的子類型,例如 PropertyRequiredError
。而 instanceof
檢查對于新的繼承類也適用。所以這是面向未來的做法。
還有一點很重要,在 catch
遇到了未知的錯誤,它會在 (**)
行將該錯誤再次拋出。catch
塊只知道如何處理 validation 錯誤和語法錯誤,而其他錯誤(由代碼中的拼寫錯誤或其他未知原因?qū)е碌模?yīng)該被扔出(fall through)。
ValidationError
類是非常通用的。很多東西都可能出錯。對象的屬性可能缺失或者屬性可能有格式錯誤(例如 age
屬性的值為一個字符串而不是數(shù)字)。讓我們針對缺少屬性的錯誤來制作一個更具體的 PropertyRequiredError
類。它將攜帶有關(guān)缺少的屬性的相關(guān)信息。
class ValidationError extends Error {
constructor(message) {
super(message);
this.name = "ValidationError";
}
}
class PropertyRequiredError extends ValidationError {
constructor(property) {
super("No property: " + property);
this.name = "PropertyRequiredError";
this.property = property;
}
}
// 用法
function readUser(json) {
let user = JSON.parse(json);
if (!user.age) {
throw new PropertyRequiredError("age");
}
if (!user.name) {
throw new PropertyRequiredError("name");
}
return user;
}
// try..catch 的工作示例
try {
let user = readUser('{ "age": 25 }');
} catch (err) {
if (err instanceof ValidationError) {
alert("Invalid data: " + err.message); // Invalid data: No property: name
alert(err.name); // PropertyRequiredError
alert(err.property); // name
} else if (err instanceof SyntaxError) {
alert("JSON Syntax Error: " + err.message);
} else {
throw err; // 未知 error,將其再次拋出
}
}
這個新的類 PropertyRequiredError
使用起來很簡單:我們只需要傳遞屬性名:new PropertyRequiredError(property)
。人類可讀的 message
是由 constructor 生成的。
請注意,在 PropertyRequiredError
constructor 中的 this.name
是通過手動重新賦值的。這可能會變得有些乏味 —— 在每個自定義 error 類中都要進行 this.name = <class name>
賦值操作。我們可以通過創(chuàng)建自己的“基礎(chǔ)錯誤(basic error)”類來避免這種情況,該類進行了 this.name = this.constructor.name
賦值。然后讓所有我們自定義的
error 都從這個“基礎(chǔ)錯誤”類進行繼承。
讓我們稱之為 MyError
。
這是帶有 MyError
以及其他自定義的 error 類的代碼,已進行簡化:
class MyError extends Error {
constructor(message) {
super(message);
this.name = this.constructor.name;
}
}
class ValidationError extends MyError { }
class PropertyRequiredError extends ValidationError {
constructor(property) {
super("No property: " + property);
this.property = property;
}
}
// name 是對的
alert( new PropertyRequiredError("field").name ); // PropertyRequiredError
現(xiàn)在自定義的 error 短了很多,特別是 ValidationError
,因為我們擺脫了 constructor 中的 "this.name = ..."
這一行。
在上面代碼中的函數(shù) readUser
的目的就是“讀取用戶數(shù)據(jù)”。在這個過程中可能會出現(xiàn)不同類型的 error。目前我們有了 SyntaxError
和 ValidationError
,但是將來,函數(shù) readUser
可能會不斷壯大,并可能會產(chǎn)生其他類型的 error。
調(diào)用 readUser
的代碼應(yīng)該處理這些 error。現(xiàn)在它在 catch
塊中使用了多個 if
語句來檢查 error 類,處理已知的 error,并再次拋出未知的 error。
該方案是這樣的:
try {
...
readUser() // 潛在的 error 源
...
} catch (err) {
if (err instanceof ValidationError) {
// 處理 validation error
} else if (err instanceof SyntaxError) {
// 處理 syntax error
} else {
throw err; // 未知 error,再次拋出它
}
}
在上面的代碼中,我們可以看到兩種類型的 error,但是可以有更多。
如果 readUser
函數(shù)會產(chǎn)生多種 error,那么我們應(yīng)該問問自己:我們是否真的想每次都一一檢查所有的 error 類型?
通常答案是 “No”:我們希望能夠“比它高一個級別”。我們只想知道這里是否是“數(shù)據(jù)讀取異?!?—— 為什么發(fā)生了這樣的 error 通常是無關(guān)緊要的(error 信息描述了它)?;蛘撸绻覀冇幸环N方式能夠獲取 error 的詳細信息那就更好了,但前提是我們需要。
我們所描述的這項技術(shù)被稱為“包裝異常”。
ReadError
? 來表示一般的“數(shù)據(jù)讀取” error。ValidationError
? 和 ?SyntaxError
?,并生成一個 ?ReadError
? 來進行替代。ReadError
? 會把對原始 error 的引用保存在其 ?cause
? 屬性中。之后,調(diào)用 readUser
的代碼只需要檢查 ReadError
,而不必檢查每種數(shù)據(jù)讀取 error。并且,如果需要更多 error 細節(jié),那么可以檢查 readUser
的 cause
屬性。
下面的代碼定義了 ReadError
,并在 readUser
和 try..catch
中演示了其用法:
class ReadError extends Error {
constructor(message, cause) {
super(message);
this.cause = cause;
this.name = 'ReadError';
}
}
class ValidationError extends Error { /*...*/ }
class PropertyRequiredError extends ValidationError { /* ... */ }
function validateUser(user) {
if (!user.age) {
throw new PropertyRequiredError("age");
}
if (!user.name) {
throw new PropertyRequiredError("name");
}
}
function readUser(json) {
let user;
try {
user = JSON.parse(json);
} catch (err) {
if (err instanceof SyntaxError) {
throw new ReadError("Syntax Error", err);
} else {
throw err;
}
}
try {
validateUser(user);
} catch (err) {
if (err instanceof ValidationError) {
throw new ReadError("Validation Error", err);
} else {
throw err;
}
}
}
try {
readUser('{bad json}');
} catch (e) {
if (e instanceof ReadError) {
alert(e);
// Original error: SyntaxError: Unexpected token b in JSON at position 1
alert("Original error: " + e.cause);
} else {
throw e;
}
}
在上面的代碼中,readUser
正如所描述的那樣正常工作 —— 捕獲語法和驗證(validation)錯誤,并拋出 ReadError
(對于未知錯誤將照常再次拋出)。
所以外部代碼檢查 instanceof ReadError
,并且它的確是。不必列出所有可能的 error 類型。
這種方法被稱為“包裝異常(wrapping exceptions)”,因為我們將“低級別”的異常“包裝”到了更抽象的 ReadError
中。它被廣泛應(yīng)用于面向?qū)ο蟮木幊讨小?
Error
? 和其他內(nèi)建的 error 類中進行繼承,。我們只需要注意 ?name
? 屬性以及不要忘了調(diào)用 ?super
?。instanceof
? 來檢查特定的 error。但有時我們有來自第三方庫的 error 對象,并且在這兒沒有簡單的方法來獲取它的類。那么可以將 ?name
? 屬性用于這一類的檢查。err.cause
?,但這不是嚴(yán)格要求的。創(chuàng)建一個繼承自內(nèi)建類 SyntaxError
的類 FormatError
。
它應(yīng)該支持 message
,name
和 stack
屬性。
用例:
let err = new FormatError("formatting error");
alert( err.message ); // formatting error
alert( err.name ); // FormatError
alert( err.stack ); // stack
alert( err instanceof FormatError ); // true
alert( err instanceof SyntaxError ); // true(因為它繼承自 SyntaxError)
class FormatError extends SyntaxError {
constructor(message) {
super(message);
this.name = this.constructor.name;
}
}
let err = new FormatError("formatting error");
alert( err.message ); // formatting error
alert( err.name ); // FormatError
alert( err.stack ); // stack
alert( err instanceof SyntaxError ); // true
Copyright©2021 w3cschool編程獅|閩ICP備15016281號-3|閩公網(wǎng)安備35020302033924號
違法和不良信息舉報電話:173-0602-2364|舉報郵箱:jubao@eeedong.com
掃描二維碼
下載編程獅App
編程獅公眾號
聯(lián)系方式:
更多建議: