網站搜尋

javascript - `throw new Error` 和 `throw someObject` 有什麼區別?


我想編寫一個通用的錯誤處理器,用來捕捉在程式的任何地方故意拋出的自訂錯誤。

當我像以下程式碼中那樣使用 throw new Error('sample') 時

try {
    throw new Error({'hehe':'haha'});
    // throw new Error('hehe');
} catch(e) {
    alert(e);
    console.log(e);
}

在 Firefox 中,日誌顯示為 Error: [object Object],我無法解析該對象。

對於第二個 throw,日誌顯示為:Error: hehe

而當我做的時候

try {
    throw ({'hehe':'haha'});
} catch(e) {
    alert(e);
    console.log(e);
}

控制台顯示為:Object { hehe="haha"},我能夠訪問錯誤屬性。

有什麼不同?

區別如同程式碼中所見嗎?像字串會直接以字串傳遞,而物件會以物件傳遞,但語法會不同嗎?

我還沒有嘗試丟擲錯誤物件……我之前只丟擲過字串。

除了上述提到的兩種方法之外,還有其他方法嗎?

答案#1

在 JavaScript 中,throw new Error(something) 和 throw something 的區別在於 throw new Error(something) 將傳入的錯誤包裝成以下格式:

{name:'Error', message:something}

throw something 將會原樣拋出該字串/物件。

throw new Error(something) 的一個好處是,當你 console.log 捕獲的錯誤對象時,你會得到像這樣的堆疊追蹤:

Error: <something>
    at foo (<anonymous>:3:11)
    at <anonymous>:9:1

這兩種方法都不會允許在 try 區塊中進一步執行任何程式碼。

這裡有一個關於 Error 物件以及如何拋出自定義錯誤的好解釋

錯誤物件

在發生錯誤時,我們能從中提取什麼?所有瀏覽器中的 Error 對象都支持以下兩個屬性:

  • 名稱:錯誤的名稱,或者更具體地說,錯誤所屬的建構函數的名稱。

  • 訊息:錯誤的描述,這個描述會根據瀏覽器而有所不同。

name 屬性可以返回六個可能的值,如前所述,這些值對應於錯誤構造函數的名稱。它們是:

Error Name          Description

EvalError           An error in the eval() function has occurred.

RangeError          Out of range number value has occurred.

ReferenceError      An illegal reference has occurred.

SyntaxError         A syntax error within code inside the eval() function has occurred.
                    All other syntax errors are not caught by try/catch/finally, and will
                    trigger the default browser error message associated with the error. 
                    To catch actual syntax errors, you may use the onerror event.

TypeError           An error in the expected variable type has occurred.

URIError            An error when encoding or decoding the URI has occurred 
                   (ie: when calling encodeURI()).

拋出你自己的錯誤(例外)

與其等待六種錯誤類型之一發生後才自動將控制權從 try 區塊轉移到 catch 區塊,你也可以明確地拋出自己的例外,以便在需要時強制這種轉移。這對於創建自己對錯誤的定義以及何時應將控制權轉移到 catch 非常有用。

答案#2

丟出「我是邪惡的」

throw 將會 終止 進一步的執行,並在 catch 錯誤時暴露訊息字串。

try {
  throw "I'm Evil"
  console.log("You'll never reach to me", 123465)
} catch (e) {
  console.log(e); // I'm Evil
}

由於終止,throw 之後的控制台將永遠不會被執行。


throw new Error("我很壞")

throw new Error 會觸發一個包含兩個參數 name 與 message 的錯誤事件。它也會終止後續執行

try {
  throw new Error("I'm Evil")
  console.log("You'll never reach to me", 123465)
} catch (e) {
  console.log(e.name, e.message); // Error I'm Evil
}

throw Error("我很邪惡")

為了完整性,這樣也可以,但從技術上來說這不是正確的做法 -

try {
  throw Error("I'm Evil")
  console.log("You'll never reach to me", 123465)
} catch (e) {
  console.log(e.name, e.message); // Error I'm Evil
}

console.log(typeof(new Error("hello"))) // object
console.log(typeof(Error)) // function

答案#3

以下文章或許會更詳細地探討哪個選擇更好;throw 'An error' 或 throw new Error('An error'):

http://www.nczonline.net/blog/2009/03/10/the-art-of-throwing-javascript-errors-part-2/

這表明後者(new Error())更可靠,因為像 Internet Explorer 和 Safari(版本不確定)的瀏覽器使用前者時不會正確報告訊息。

這樣做會導致拋出錯誤,但並非所有瀏覽器的反應都如你所預期。Firefox、Opera 和 Chrome 都會顯示「未捕獲的例外」訊息,然後附上訊息字串。Safari 和 Internet Explorer 則只會拋出「未捕獲的例外」錯誤,根本不提供訊息字串。顯然,從除錯的角度來看,這是不理想的。

答案#4

摘要:它們是等價的 Error(x) === new Error(x)。

// this:
const x = Error('I was created using a function call!');
// has the same functionality as this:
const y = new Error('I was constructed via the "new" keyword!');

來源:https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Error

throw 和 throw Error 在功能上是等效的。但當你捕獲它們並序列化到 console.log 時,它們的序列化方式並不完全相同:

throw 'Parameter is not a number!';
throw new Error('Parameter is not a number!');
throw Error('Parameter is not a number!');

上述的 Console.log(e) 將會產生兩個不同的結果:

Parameter is not a number!
Error: Parameter is not a number!
Error: Parameter is not a number!

答案#5

你先提到這段代碼:

throw new Error('sample')

然後在你的第一個例子中,你寫道:

throw new Error({'hehe':'haha'}) 

第一個 Error 物件其實會很有用,因為它預期的是一個字串值,在這個例子中是 'sample'。第二個則不會有用,因為你試圖傳入一個物件,而它預期的是字串,且不會顯示有用的錯誤訊息。

錯誤物件將會有 "message" 屬性,其值將為 'sample'。

答案#6

Error 建構子用於創建錯誤物件。當運行時發生錯誤時,會拋出錯誤物件。Error 物件也可以用作用戶定義例外的基底物件。

使用者定義的錯誤會透過 throw 陳述式拋出。程式控制權將傳遞到呼叫堆疊中的第一個 catch 區塊。

使用與不使用 Error 物件拋出錯誤的差異:

throw {'hehe':'haha'};

在 Chrome 開發者工具中看起來像這樣:

Chrome 告訴我們我們有一個未捕獲的錯誤,這只是一個 JS 對象。這個對象本身可能包含關於錯誤的資訊,但我們仍然無法立即知道它的來源。當我們在撰寫代碼和進行除錯時,這並不是很有用。

throw new Error({'hehe':'haha'}); 

在 Chrome 開發者工具中看起來像這樣:

使用 Error 物件拋出的錯誤,在展開時會給我們一個堆疊追蹤。這為我們提供了錯誤精確來源的寶貴資訊,這在調試程式碼時通常十分有用。進一步注意,錯誤顯示為 [object Object],這是因為 Error 建構函數期望第一個參數為訊息字串。當它收到一個物件時,它會將其強制轉換為字串。

答案#7

Error 類別 包含除錯資訊,例如錯誤的呼叫堆疊,作為其實例的屬性。JavaScript 直譯器知道如何將 Error 實例序列化為資訊完整的錯誤訊息字串,其結構也可以被除錯軟體(例如瀏覽器開發工具)使用,以建立更具資訊性的 GUI 錯誤表示。因此,通常丟出 Error 類別的實例比單純拋出,例如描述錯誤的字串或代表錯誤代碼的數字,更有用。

使用自訂錯誤

自己創建 Error 的子類特別有用,這讓你能夠用描述性名稱和機器可讀的方式唯一識別不同類型的錯誤……

  • 除錯線索,
  • 用於更好的面向使用者的錯誤訊息的資訊,或
  • 幫助從錯誤中恢復的資訊。

然後,當你處理錯誤時,你可以使用簡單清晰的 instanceof 運算子來檢查發生了 哪一種類型 的錯誤。例如:

class DangerousWaterCurrent extends Error {
    constructor(waterSpeed){
        super(`These waters are moving at ${waterSpeed} metres per second - too fast to cross!`) // Provide a `message` argument to the Error() constructor
        this.waterSpeed = waterSpeed // This passes some context about why/how the error occurred back to whichever function is going to catch & handle it
    }
}

// ...later...

try {
    swimAcrossRiver(river)
} catch (thrownValue) {
    if (thrownValue instanceof DangerousWaterCurrent) {
        if (thrownValue.waterSpeed <= 3){ 
            paddleKayak(river)
        } else {
            constructBridge(river)
        }
    } else {
        throw thrownValue // "Re-throw" the error back up the execution chain, for someone else to handle
    }
}

new Error() 與 Error()

有一個「方便的」簡寫方法來建立 Error 的實例:通過呼叫 Error(message),而不是像建立一般類別實例那樣使用 new Error(message)。這是語言設計者特意插入的例外規則。其他語言內建類別也有類似的簡寫,例如 Number() 和 String()。它們也允許你將這些類別當作函數一樣使用 () 來呼叫,而不是當作類別。JS 不允許一般類別這麼做,儘管它們在「類別」的語法糖下實際上都是函數。在 REPL 中嘗試看看:

> class E extends Error {}
> Error(); "a value"
"a value"
> E(); "a value"
Uncaught TypeError: Class constructor E cannot be invoked without 'new'
    at <anonymous>:2:1

對設計的更廣泛看法:就個人而言,我認為這個設計決策是個錯誤,因為它為 JavaScript 的規則增加了更多例外——這意味著程式設計師需要學習更多內容,語言翻譯器/解譯器也需要處理更多情況。與其使用 C++/Java 的 new 關鍵字,不如直接像調用函數一樣調用類(如 Number("abc123"))應該擁有 new 關鍵字目前具備的屬性:類的 constructor 函數應該被執行。在該函數內,this 應綁定到該實例,然後隱式返回。這樣一來,語言中的 new 關鍵字就可以被捨棄。這正是 Python 語法的運作方式,它更簡單、更易讀,也更便捷。

答案#8

你可以將 throw 當作物件

throw ({message: 'This Failed'})

那麼例如在你的 try/catch 中

try {
//
} catch(e) {
    console.log(e); //{message: 'This Failed'}
    console.log(e.message); //This Failed
}

或者只是拋出一個字串錯誤

throw ('Your error')

try {
//
} catch(e) {
    console.log(e); //Your error
}

throw new Error //only accept a string

答案#9

摘要

throw new Error('problem') 捕捉了錯誤發生位置的多個屬性。

throw 'problem' 不會

new Error('message') 捕獲執行堆疊及其他

使用 Error 物件可以讓你在丟出錯誤的那一點捕捉執行堆疊。因此,當錯誤被傳遞到錯誤處理樹上時,這個堆疊快照也會一起傳遞。

所以在我的程式碼中插入 throw "test error" 會導致:

而 throw new Error('test error') 會導致:

你可以看到,原生的 Error 物件會在我拋出錯誤的那一刻捕捉堆疊,並將其提供給捕捉錯誤的任何程式。這讓我在除錯時更容易追蹤問題。

除此之外,它還會捕捉像 fileName、lineNumber 和 columnNumber 這樣的屬性。

如果你使用堆疊追蹤,它是為了讓例外追蹤器幫你記錄

在這種情況下,堆疊將被列印到瀏覽器控制台,但如果你使用像 Appsignal 或 Bugsnag 這樣的 Javascript 錯誤日誌工具,那麼該堆疊也會在它們中可用。如果你檢查錯誤對象,你可以直接訪問堆疊快照:

err = new Error('test')
err.stack

我用來決定使用哪種格式的啟發式方法

當我不打算捕捉例外時,我會使用 new Error('problem')

當我因為應用程式中發生了某些意外或越界的情況而丟出錯誤時,比如說本地資料存儲已損壞,我可能處於不想處理它的情況,但我確實想標記它。在這種情況下,我會使用 Error 物件,這樣我就能擁有那個堆疊快照。

通過使用 throw new Error('資料庫已損壞'),我可以更容易地追蹤回發生了什麼事。

當我打算捕捉例外時,我使用 throw 'problem'

編輯 - 再讀一次這段內容,我認為接下來的部分需要一些謹慎。通常非常具體地指定你要捕捉哪種錯誤是個好主意,否則你可能會捕捉到一些本來希望向上傳遞的錯誤。一般來說,最好創建一個特定的錯誤類型並捕捉那個特定的錯誤(或消息字串)。這樣可以讓你未預料到的錯誤冒泡到表面。

如果這個錯誤是我預計會捕捉和處理的預期錯誤,那麼我從堆疊快照中獲得的幫助不會太大。

所以,假設我使用一個 http 服務,它返回了 500 HTTP 代碼。我可能會將其視為一個錯誤,我會 throw "responseCode=500",然後隨後捕捉並處理。