處理JavaScript錯誤的權威指南

處理JavaScript錯誤的權威指南

墨菲定律指出,任何可能出錯的事情最終都會出錯。這在程式設計世界中應用得太好了。如果您建立一個應用程式,您很可能會產生錯誤和其他問題。JavaScript中的錯誤就是這樣一個常見問題!

軟體產品的成功取決於其建立者在傷害使用者之前解決這些問題的能力。在所有程式語言中, JavaScript因其平均錯誤處理設計而臭名昭著。

如果您正在構建一個JavaScript應用程式,那麼您很有可能會在某一時刻弄亂資料型別。如果不是這樣,那麼您最終可能會將undefined替換為null或將三等號運算子 ( ===) 替換為雙等號運算子 ( ==)。

犯錯是人之常情。這就是為什麼我們將向您展示您需要了解的有關處理JavaScript錯誤的所有資訊。

本文將引導您瞭解JavaScript中的基本錯誤,並解釋您可能遇到的各種錯誤。然後,您將學習如何識別和修復這些錯誤。還有一些技巧可以在生產環境中有效地處理錯誤。

  1. 什麼是JavaScript錯誤?
  2. JavaScript中的錯誤型別
  3. 建立自定義錯誤型別
  4. JavaScript 中最常見的10個錯誤
  5. 如何識別和防止JavaScript中的錯誤
  6. 處理JavaScript錯誤的最佳實踐

什麼是JavaScript錯誤?

程式設計錯誤是指程式無法正常執行的情況。當程式不知道如何處理手頭的工作時,可能會發生這種情況,例如嘗試開啟不存在的檔案或在沒有網路連線的情況下訪問基於Web的API端點時。

這些情況促使程式向使用者丟擲錯誤,說明它不知道如何繼續。該程式收集儘可能多的有關錯誤的資訊,然後報告它無法繼續前進。

聰明的程式設計師試圖預測和覆蓋這些場景,這樣使用者就不必獨立地找出像“404”這樣的技術錯誤資訊。相反,它們顯示了一條更容易理解的資訊:“找不到該頁面。”

JavaScript中的錯誤是在發生程式設計錯誤時顯示的物件。這些物件包含有關錯誤型別、導致錯誤的語句以及發生錯誤時的堆疊跟蹤的大量資訊。JavaScript還允許程式設計師建立自定義錯誤,以便在除錯問題時提供額外資訊。

錯誤的屬性

現在JavaScript錯誤的定義已經很清楚了,是時候深入研究細節了。

JavaScript中的錯誤帶有某些標準和自定義屬性,有助於理解錯誤的原因和影響。預設情況下,JavaScript中的錯誤包含三個屬性:

  1. message : 攜帶錯誤資訊的字串值
  2. name:發生的錯誤型別(我們將在下一節深入探討)
  3. stack:發生錯誤時執行的程式碼的堆疊跟蹤。

此外,error還可以攜帶columnNumber、lineNumber、fileName等屬性,以更好地描述錯誤。但是,這些屬性不是標準的,可能會出現在JavaScript應用程式生成的每個錯誤物件中,也可能不會出現。

瞭解堆疊跟蹤

堆疊跟蹤是發生異常或警告等事件時程式所在的方法呼叫列表。這是伴隨異常的示例堆疊跟蹤的樣子:

堆疊跟蹤示例

堆疊跟蹤示例

如您所見,它首先列印錯誤名稱和訊息,然後是被呼叫的方法列表。每個方法呼叫都說明其原始碼的位置以及呼叫它的行。您可以使用這些資料瀏覽您的程式碼庫並確定是哪段程式碼導致了錯誤。

此方法列表以堆疊方式排列。它顯示了您的異常首次引發的位置以及它如何通過堆疊的方法呼叫傳播。為異常實現捕獲不會讓它通過堆疊向上傳播並使您的程式崩潰。但是,您可能希望在某些情況下故意不捕獲致命錯誤以使程式崩潰。

錯誤與異常

大多數人通常將錯誤和異常視為同一件事。但是,必須注意它們之間的細微但根本的區別。

異常是已丟擲的錯誤物件。

為了更好地理解這一點,讓我們舉一個簡單的例子。以下是如何在JavaScript中定義錯誤:

Plain text
Copy to clipboard
Open code in new window
EnlighterJS 3 Syntax Highlighter
const wrongTypeError = TypeError("Wrong type found, expected character")
const wrongTypeError = TypeError("Wrong type found, expected character")
const wrongTypeError = TypeError("Wrong type found, expected character")

這就是wrongTypeError物件變成異常的方式:

Plain text
Copy to clipboard
Open code in new window
EnlighterJS 3 Syntax Highlighter
throw wrongTypeError
throw wrongTypeError
throw wrongTypeError

然而,大多數人傾向於使用在丟擲錯誤物件時定義錯誤物件的簡寫形式:

Plain text
Copy to clipboard
Open code in new window
EnlighterJS 3 Syntax Highlighter
throw TypeError("Wrong type found, expected character")
throw TypeError("Wrong type found, expected character")
throw TypeError("Wrong type found, expected character")

這是標準做法。然而,這也是開發人員傾向於混淆異常和錯誤的原因之一。因此,即使您使用速記來快速完成工作,瞭解基礎知識也至關重要。

JavaScript中的錯誤型別

JavaScript中有一系列預定義的錯誤型別。只要程式設計師沒有明確處理應用程式中的錯誤,它們就會由 JavaScript 執行時自動選擇和定義。

本節將引導您瞭解JavaScript中一些最常見的錯誤型別,並瞭解它們發生的時間和原因。

範圍錯誤

當變數設定為超出其合法值範圍時,會引發RangeError。它通常發生在將值作為引數傳遞給函式時,並且給定值不在函式引數的範圍內。使用文件記錄不佳的第三方庫時,有時修復起來會很棘手,因為您需要知道引數的可能值範圍才能傳遞正確的值。

RangeError發生的一些常見場景是:

  • 試圖通過Array建構函式建立一個非法長度的陣列。
  • 將錯誤值傳遞給數字方法,如toExponential()toPrecision(),toFixed()等。
  • 將非法值傳遞給字串函式,例如normalize().

參考錯誤

當您的程式碼中的變數引用出現問題時,就會發生ReferenceError。您可能忘記在使用變數之前為其定義值,或者您可能試圖在程式碼中使用不可訪問的變數。在任何情況下,通過堆疊跟蹤提供了充足的資訊來查詢和修復有錯誤的變數引用。

ReferenceErrors發生的一些常見原因是:

  • 在變數名中打錯字。
  • 試圖訪問其範圍之外的塊範圍變數。
  • 在載入之前從外部庫引用全域性變數(例如 $ from jQuery)。

語法錯誤

這些錯誤是最容易修復的錯誤之一,因為它們表明程式碼語法有錯誤。由於JavaScript是一種解釋而不是編譯的指令碼語言,因此當應用程式執行包含錯誤的指令碼時會丟擲這些指令碼語言。在編譯語言的情況下,在編譯過程中會識別出此類錯誤。因此,在修復之前不會建立應用程式二進位制檔案。

可能發生SyntaxErrors的一些常見原因是:

  • 缺少引號
  • 缺少右括號
  • 花括號或其他字元的不正確對齊

最好在IDE中使用linting工具在此類錯誤出現在瀏覽器之前為您識別這些錯誤。

型別錯誤

TypeError是JavaScript應用程式中最常見的錯誤之一。當某些值不是特定的預期型別時,會建立此錯誤。發生時的一些常見情況是:

  • 呼叫不是方法的物件。
  • 試圖訪問空物件或未定義物件的屬性
  • 將字串視為數字,反之亦然

發生TypeError的可能性還有很多。稍後我們將檢視一些著名的例項並學習如何修復它們。

內部錯誤

當JavaScript執行時引擎中發生異常時使用InternalError型別。它可能表示也可能不表示您的程式碼存在問題。

通常,InternalError僅在兩種情況下發生:

  • 當JavaScript執行時的補丁或更新帶有引發異常的錯誤時(這種情況很少發生)
  • 當你的程式碼包含對JavaScript引擎來說太大的實體時(例如太多的switch case、太大的陣列初始化器、太多的遞迴)

解決此錯誤的最合適方法是通過錯誤訊息確定原因,並在可能的情況下重構您的應用程式邏輯,以消除JavaScript引擎上的工作負載突然激增。

URI錯誤

URIError發生在全域性URI處理函式如decodeURIComponent被非法使用時。它通常表示傳遞給方法呼叫的引數不符合URI標準,因此沒有被方法正確解析

診斷這些錯誤通常很容易,因為您只需要檢查引數是否存在畸形。

評估錯誤

當函式eval()呼叫發生錯誤時會發生EvalError 。eval()函式用於執行儲存在字串中的 JavaScript 程式碼。但是,由於安全問題,強烈建議不要使用eval()函式,並且當前的ECMAScript規範不再丟擲EvalError類,因此存在此錯誤型別只是為了保持與舊版JavaScript程式碼的向後相容性。

如果您使用的是舊版本的JavaScript,則可能會遇到此錯誤。無論如何,最好調查eval()函式呼叫中執行的程式碼是否有任何異常。

建立自定義錯誤型別

雖然JavaScript提供了足夠的錯誤型別列表來涵蓋大多數場景,但如果列表不滿足您的要求,您始終可以建立新的錯誤型別。這種靈活性的基礎在於JavaScript允許您使用throw命令逐字地丟擲任何東西。

因此,從技術上講,這些宣告是完全合法的:

Plain text
Copy to clipboard
Open code in new window
EnlighterJS 3 Syntax Highlighter
throw 8
throw "An error occurred"
throw 8 throw "An error occurred"
throw 8
throw "An error occurred"

但是,丟擲原始資料型別不會提供有關錯誤的詳細資訊,例如其型別、名稱或隨附的堆疊跟蹤。為了解決這個問題並標準化錯誤處理過程,我們提供了Error這個類。也不鼓勵在丟擲異常時使用原始資料型別。

您可以擴充套件Error類以建立您的自定義錯誤類。以下是如何執行此操作的基本示例:

Plain text
Copy to clipboard
Open code in new window
EnlighterJS 3 Syntax Highlighter
class ValidationError extends Error {
constructor(message) {
super(message);
this.name = "ValidationError";
}
}
class ValidationError extends Error { constructor(message) { super(message); this.name = "ValidationError"; } }
class ValidationError extends Error {
constructor(message) {
super(message);
this.name = "ValidationError";
}
}

您可以通過以下方式使用它:

Plain text
Copy to clipboard
Open code in new window
EnlighterJS 3 Syntax Highlighter
throw ValidationError("Property not found: name")
throw ValidationError("Property not found: name")
throw ValidationError("Property not found: name")

然後您可以使用instanceof關鍵字識別它:

Plain text
Copy to clipboard
Open code in new window
EnlighterJS 3 Syntax Highlighter
try {
validateForm() // code that throws a ValidationError
} catch (e) {
if (e instanceof ValidationError)
// do something
else
// do something else
}
try { validateForm() // code that throws a ValidationError } catch (e) { if (e instanceof ValidationError) // do something else // do something else }
try {
validateForm() // code that throws a ValidationError
} catch (e) {
if (e instanceof ValidationError)
// do something
else
// do something else
}

JavaScript中最常見的10個錯誤

現在您已經瞭解了常見的錯誤型別以及如何建立自定義錯誤型別,是時候看看您在編寫JavaScript程式碼時會遇到的一些最常見的錯誤了。

1. Uncaught RangeError

在幾種不同的情況下,Google Chrome中會出現此錯誤。首先,如果您呼叫遞迴函式並且它不會終止,則可能會發生這種情況。您可以在Chrome開發者控制檯中自行檢視:

帶有遞迴函式呼叫的RangeError示例

帶有遞迴函式呼叫的RangeError示例

因此,要解決此類錯誤,請確保正確定義遞迴函式的邊界情況。發生此錯誤的另一個原因是您傳遞的值超出了函式的引數範圍。這是一個例子:

帶有toExponential()呼叫的RangeError示例

帶有toExponential()呼叫的RangeError示例

錯誤訊息通常會指出您的程式碼有什麼問題。一旦你做出改變,它就會得到解決。

toExponential() 函式呼叫的輸出

toExponential() 函式呼叫的輸出

2. Uncaught TypeError: Cannot set property

當您在未定義的引用上設定屬性時會發生此錯誤。您可以使用此程式碼重現該問題:

Plain text
Copy to clipboard
Open code in new window
EnlighterJS 3 Syntax Highlighter
var list
list.count = 0
var list list.count = 0
var list
list.count = 0

這是您將收到的輸出:

型別錯誤示例

型別錯誤示例

要修復此錯誤,請在訪問其屬性之前使用值初始化引用。以下是修復後的外觀:

如何修復型別錯誤

如何修復型別錯誤

3. Uncaught TypeError: Cannot read property

這是JavaScript中最常出現的錯誤之一。當您嘗試讀取屬性或呼叫未定義物件的函式時,會發生此錯誤。您可以通過在Chrome開發人員控制檯中執行以下程式碼來非常輕鬆地重現它:

Plain text
Copy to clipboard
Open code in new window
EnlighterJS 3 Syntax Highlighter
var func
func.call()
var func func.call()
var func
func.call()

這是輸出:

帶有未定義函式的TypeError示例

帶有未定義函式的TypeError示例

未定義的物件是導致此錯誤的眾多可能原因之一。此問題的另一個突出原因可能是在呈現 UI 時未正確初始化狀態。這是來自React應用程式的真實示例:

Plain text
Copy to clipboard
Open code in new window
EnlighterJS 3 Syntax Highlighter
import React, { useState, useEffect } from "react";
const CardsList = () => {
const [state, setState] = useState();
useEffect(() => {
setTimeout(() => setState({ items: ["Card 1", "Card 2"] }), 2000);
}, []);
return (
<>
{state.items.map((item) => (
<li key={item}>{item}</li>
))}
</>
);
};
export default CardsList;
import React, { useState, useEffect } from "react"; const CardsList = () => { const [state, setState] = useState(); useEffect(() => { setTimeout(() => setState({ items: ["Card 1", "Card 2"] }), 2000); }, []); return ( <> {state.items.map((item) => ( <li key={item}>{item}</li> ))} </> ); }; export default CardsList;
import React, { useState, useEffect } from "react";
const CardsList = () => {
const [state, setState] = useState();
useEffect(() => {
setTimeout(() => setState({ items: ["Card 1", "Card 2"] }), 2000);
}, []);
return (
<>
{state.items.map((item) => (
<li key={item}>{item}</li>
))}
</>
);
};
export default CardsList;

該應用程式從一個空狀態容器開始,並在延遲2秒後提供一些專案。延遲用於模擬網路呼叫。即使您的網路速度非常快,您仍然會面臨輕微的延遲,因為該元件將至少呈現一次。如果您嘗試執行此應用程式,您將收到以下錯誤:

瀏覽器中的TypeError堆疊跟蹤

瀏覽器中的TypeError堆疊跟蹤

這是因為,在渲染時,狀態容器是未定義的;因此,它不存在任何財產items。修復這個錯誤很容易。您只需要為狀態容器提供初始預設值。

Plain text
Copy to clipboard
Open code in new window
EnlighterJS 3 Syntax Highlighter
// ...
const [state, setState] = useState({items: []});
// ...
// ... const [state, setState] = useState({items: []}); // ...
// ...
const [state, setState] = useState({items: []});
// ...

現在,在設定延遲之後,您的應用程式將顯示類似的輸出:

程式碼輸出

程式碼輸出

程式碼中的確切修復可能會有所不同,但這裡的本質是始終在使用變數之前正確初始化它們。

4. TypeError: ‘undefined’ is not an object

當您嘗試訪問未定義物件的屬性或呼叫未定義物件的方法時,會在Safari中發生此錯誤。您可以從上面執行相同的程式碼來自己重現錯誤。

帶有未定義函式的TypeError示例

帶有未定義函式的TypeError示例

這個錯誤的解決方法也是一樣的——確保你已經正確地初始化了你的變數,並且在訪問一個屬性或方法時它們不是未定義的。

5. TypeError: null is not an object

這又與前面的錯誤相似。它發生在Safari上,這兩個錯誤之間的唯一區別是,當正在訪問其屬性或方法的物件null不是undefined. 您可以通過執行以下程式碼來重現這一點:

Plain text
Copy to clipboard
Open code in new window
EnlighterJS 3 Syntax Highlighter
var func = null
func.call()
var func = null func.call()
var func = null
func.call()

這是您將收到的輸出:

帶有null函式的TypeError示例

帶有null函式的TypeError示例

因為null是顯式設定為變數的值,而不是由JavaScript自動分配的值。僅當您嘗試訪問null自己設定的變數時,才會發生此錯誤。因此,您需要重新訪問您的程式碼並檢查您編寫的邏輯是否正確。

6. TypeError: Cannot read property ‘length’

當您嘗試讀取nullundefined物件的長度時,Chrome中會出現此錯誤。這個問題的原因和前面的問題類似,但是在處理列表的時候出現的頻率比較高;因此值得特別提及。以下是重現問題的方法:

帶有未定義物件的TypeError示例

帶有未定義物件的TypeError示例

但是,在較新版本的Chrome中,此錯誤報告為Uncaught TypeError: Cannot read properties of undefined. 這是它現在的樣子:

在較新的Chrome版本上帶有未定義物件的TypeError示例

在較新的Chrome版本上帶有未定義物件的TypeError示例

再次,修復是確保您嘗試訪問其長度的物件存在並且未設定為null.

7. TypeError: ‘undefined’ is not a function

當您嘗試呼叫指令碼中不存在的方法或該方法存在但無法在呼叫上下文中引用時,會發生此錯誤。這個錯誤通常發生在谷歌瀏覽器中,您可以通過檢查丟擲錯誤的程式碼行來解決它。如果您發現拼寫錯誤,請修復它並檢查它是否能解決您的問題。

如果您在程式碼中使用了自引用關鍵字this,如果this沒有適當地繫結到您的上下文,則可能會出現此錯誤。考慮下面的程式碼:

Plain text
Copy to clipboard
Open code in new window
EnlighterJS 3 Syntax Highlighter
function showAlert() {
alert("message here")
}
document.addEventListener("click", () => {
this.showAlert();
})
function showAlert() { alert("message here") } document.addEventListener("click", () => { this.showAlert(); })
function showAlert() {
alert("message here")
}
document.addEventListener("click", () => {
this.showAlert();
})

如果執行上述程式碼,它將丟擲我們討論過的錯誤。之所以會發生這種情況,是因為作為事件偵聽器傳遞的匿名函式正在document的上下文中執行。

相比之下,showAlert函式是在window的上下文中定義的。

為了解決這個問題,您必須通過將函式與bind()方法繫結來傳遞對函式的正確引用:

Plain text
Copy to clipboard
Open code in new window
EnlighterJS 3 Syntax Highlighter
document.addEventListener("click", this.showAlert.bind(this))
document.addEventListener("click", this.showAlert.bind(this))
document.addEventListener("click", this.showAlert.bind(this))

8. ReferenceError: event is not defined

當您嘗試訪問未在呼叫範圍內定義的引用時,會發生此錯誤。這通常發生在處理事件時,因為它們經常為您提供event回撥函式中呼叫的引用。如果您忘記在函式的引數中定義事件引數或拼寫錯誤,則可能會發生此錯誤。

在Internet Explorer或Google Chrome中可能不會發生此錯誤(因為IE提供了一個全域性事件變數,並且Chrome會自動將事件變數附加到處理程式),但它可能在Firefox中發生。所以建議留意這樣的小錯誤。

9. TypeError: Assignment to constant variable

這是由於粗心造成的錯誤。如果您嘗試將新值分配給常量變數,您將遇到這樣的結果:

帶有常量物件分配的TypeError示例

帶有常量物件分配的TypeError示例

雖然現在看起來很容易修復,但想象一下數百個這樣的變數宣告,其中一個被錯誤地定義為const而不是let! 與PHP等其他指令碼語言不同,在JavaScript中宣告常量和變數的風格差別很小。因此,當您遇到此錯誤時,建議首先檢查您的宣告。如果您忘記上述引用是一個常量並將其用作變數,您也可能會遇到此錯誤。這表明您的應用程式邏輯存在粗心或缺陷。嘗試解決此問題時,請務必檢查此項。

10.(unknown): Script error

當第三方指令碼向您的瀏覽器傳送錯誤時,就會發生指令碼錯誤。此錯誤後跟(未知),因為第三方指令碼與您的應用屬於不同的域。瀏覽器隱藏了其他細節,以防止第三方指令碼洩露敏感資訊。

在不瞭解完整詳細資訊的情況下,您無法解決此錯誤。您可以執行以下操作來獲取有關該錯誤的更多資訊:

  1. 在指令碼標籤中新增crossorigin屬性。
  2. 在託管指令碼的伺服器上設定正確的Access-Control-Allow-Origin標頭。
  3. [可選] 如果您無權訪問託管指令碼的伺服器,您可以考慮使用代理將您的請求中繼到伺服器並返回到帶有正確標頭的客戶端。

一旦您可以訪問錯誤的詳細資訊,您就可以著手解決問題,這可能與第三方庫或網路有關。

如何識別和防止JavaScript中的錯誤

雖然上面討論的錯誤是JavaScript中最常見和最常見的錯誤,但您會遇到,僅僅依靠幾個示例是遠遠不夠的。在開發JavaScript應用程式時,瞭解如何檢測和防止任何型別的錯誤至關重要。以下是如何處理JavaScript中的錯誤。

手動丟擲和捕獲錯誤

處理手動或執行時丟擲的錯誤的最基本方法是捕獲它們。與大多數其他語言一樣,JavaScript提供了一組關鍵字來處理錯誤。在著手處理JavaScript應用程式中的錯誤之前,必須深入瞭解它們中的每一個。

throw

該集合的第一個也是最基本的關鍵字是throw. 很明顯,throw關鍵字用於丟擲錯誤以在JavaScript執行時手動建立異常。我們已經在本文前面討論過這個問題,這裡是這個關鍵字意義的要點:

  • 你可以throw做任何事情,包括數字、字串和Error物件。
  • 但是,不建議丟擲諸如字串和數字之類的原始資料型別,因為它們不攜帶有關錯誤的除錯資訊。
  • 例子:throw TypeError("Please provide a string")

try

try關鍵字用於指示程式碼塊可能會引發異常。它的語法是:

Plain text
Copy to clipboard
Open code in new window
EnlighterJS 3 Syntax Highlighter
try {
// error-prone code here
}
try { // error-prone code here }
try {
// error-prone code here
}

重要的是要注意,catch塊必須始終跟隨try塊才能有效地處理錯誤。

catch

catch關鍵字用於建立一個catch塊。此程式碼塊負責處理尾隨try塊捕獲的錯誤。這是它的語法:

Plain text
Copy to clipboard
Open code in new window
EnlighterJS 3 Syntax Highlighter
catch (exception) {
// code to handle the exception here
}
catch (exception) { // code to handle the exception here }
catch (exception) {
// code to handle the exception here
}

這就是你如何一起實現trycatch塊的方式:

Plain text
Copy to clipboard
Open code in new window
EnlighterJS 3 Syntax Highlighter
try {
// business logic code
} catch (exception) {
// error handling code
}
try { // business logic code } catch (exception) { // error handling code }
try {
// business logic code
} catch (exception) {
// error handling code
}

與C++或Java不同,您不能將多個catch塊附加到JavaScript中的try塊。這意味著您不能這樣做:

Plain text
Copy to clipboard
Open code in new window
EnlighterJS 3 Syntax Highlighter
try {
// business logic code
} catch (exception) {
if (exception instanceof TypeError) {
// do something
}
} catch (exception) {
if (exception instanceof RangeError) {
// do something
}
}
try { // business logic code } catch (exception) { if (exception instanceof TypeError) { // do something } } catch (exception) { if (exception instanceof RangeError) { // do something } }
try {
// business logic code
} catch (exception) {
if (exception instanceof TypeError) {
// do something
}
} catch (exception) {
if (exception instanceof RangeError) {
// do something
}
}

相反,您可以在單個catch塊中使用if...else語句或switch case語句來處理所有可能的錯誤情況。它看起來像這樣:

Plain text
Copy to clipboard
Open code in new window
EnlighterJS 3 Syntax Highlighter
try {
// business logic code
} catch (exception) {
if (exception instanceof TypeError) {
// do something
} else if (exception instanceof RangeError) {
// do something else
}
}
try { // business logic code } catch (exception) { if (exception instanceof TypeError) { // do something } else if (exception instanceof RangeError) { // do something else } }
try {
// business logic code
} catch (exception) {
if (exception instanceof TypeError) {
// do something
} else if (exception instanceof RangeError) {
// do something else
}
}

finally

finally關鍵字用於定義在處理錯誤後執行的程式碼塊。該塊在try和catch塊之後執行。

此外,無論其他兩個塊的結果如何,都會執行finally塊。這意味著即使catch塊不能完全處理錯誤或者catch塊中丟擲錯誤,直譯器也會在程式崩潰之前執行finally塊中的程式碼。

要被認為是有效的,JavaScript中的try塊需要後跟catch或finally塊。如果沒有這些,直譯器將引發SyntaxError。因此,在處理錯誤時,請確保至少遵循您的try塊。

使用onerror()方法全域性處理錯誤

onerror()方法適用於所有HTML元素,用於處理它們可能發生的任何錯誤。例如,如果img標籤找不到指定URL的影象,它會觸發其onerror方法以允許使用者處理錯誤。

通常,您會在onerror呼叫中提供另一個影象URL,以便img標記回退到。這是您可以通過JavaScript執行此操作的方法:

Plain text
Copy to clipboard
Open code in new window
EnlighterJS 3 Syntax Highlighter
const image = document.querySelector("img")
image.onerror = (event) => {
console.log("Error occurred: " + event)
}
const image = document.querySelector("img") image.onerror = (event) => { console.log("Error occurred: " + event) }
const image = document.querySelector("img")
image.onerror = (event) => {
console.log("Error occurred: " + event)
}

但是,您可以使用此功能為您的應用建立全域性錯誤處理機制。以下是您的操作方法:

Plain text
Copy to clipboard
Open code in new window
EnlighterJS 3 Syntax Highlighter
window.onerror = (event) => {
console.log("Error occurred: " + event)
}
window.onerror = (event) => { console.log("Error occurred: " + event) }
window.onerror = (event) => {
console.log("Error occurred: " + event)
}

使用此事件處理程式,您可以擺脫try...catch程式碼中的多個塊,並集中您的應用程式的錯誤處理,類似於事件處理。您可以將多個錯誤處理程式附加到視窗,以維護SOLID設計原則中的單一責任原則。直譯器將迴圈遍歷所有處理程式,直到到達適當的處理程式。

通過回撥傳遞錯誤

雖然簡單和線性函式允許錯誤處理保持簡單,但回撥會使事情複雜化。

考慮以下程式碼:

Plain text
Copy to clipboard
Open code in new window
EnlighterJS 3 Syntax Highlighter
const calculateCube = (number, callback) => {
setTimeout(() => {
const cube = number * number * number
callback(cube)
}, 1000)
}
const callback = result => console.log(result)
calculateCube(4, callback)
const calculateCube = (number, callback) => { setTimeout(() => { const cube = number * number * number callback(cube) }, 1000) } const callback = result => console.log(result) calculateCube(4, callback)
const calculateCube = (number, callback) => {
setTimeout(() => {
const cube = number * number * number
callback(cube)
}, 1000)
}
const callback = result => console.log(result)
calculateCube(4, callback)

上面的函式演示了一個非同步條件,其中一個函式需要一些時間來處理操作並稍後在回撥的幫助下返回結果。

如果您嘗試在函式呼叫中輸入字串而不是4,您將得到NaN結果。

這需要妥善處理。就是這樣:

Plain text
Copy to clipboard
Open code in new window
EnlighterJS 3 Syntax Highlighter
const calculateCube = (number, callback) => {
setTimeout(() => {
if (typeof number !== "number")
throw new Error("Numeric argument is expected")
const cube = number * number * number
callback(cube)
}, 1000)
}
const callback = result => console.log(result)
try {
calculateCube(4, callback)
} catch (e) { console.log(e) }
const calculateCube = (number, callback) => { setTimeout(() => { if (typeof number !== "number") throw new Error("Numeric argument is expected") const cube = number * number * number callback(cube) }, 1000) } const callback = result => console.log(result) try { calculateCube(4, callback) } catch (e) { console.log(e) }
const calculateCube = (number, callback) => {
setTimeout(() => {
if (typeof number !== "number")
throw new Error("Numeric argument is expected")
const cube = number * number * number
callback(cube)
}, 1000)
}
const callback = result => console.log(result)
try {
calculateCube(4, callback)
} catch (e) { console.log(e) }

這應該可以理想地解決問題。但是,如果您嘗試將字串傳遞給函式呼叫,您將收到以下資訊:

錯誤引數的錯誤示例

錯誤引數的錯誤示例

即使您在呼叫函式時實現了try-catch塊,它仍然表示錯誤未捕獲。由於超時延遲,在執行catch塊後丟擲錯誤。

這可能在網路呼叫中很快發生,在這種情況下會出現意外延遲。您需要在開發應用程式時涵蓋此類情況。

以下是在回撥中正確處理錯誤的方法:

Plain text
Copy to clipboard
Open code in new window
EnlighterJS 3 Syntax Highlighter
const calculateCube = (number, callback) => {
setTimeout(() => {
if (typeof number !== "number") {
callback(new TypeError("Numeric argument is expected"))
return
}
const cube = number * number * number
callback(null, cube)
}, 2000)
}
const callback = (error, result) => {
if (error !== null) {
console.log(error)
return
}
console.log(result)
}
try {
calculateCube('hey', callback)
} catch (e) {
console.log(e)
}
const calculateCube = (number, callback) => { setTimeout(() => { if (typeof number !== "number") { callback(new TypeError("Numeric argument is expected")) return } const cube = number * number * number callback(null, cube) }, 2000) } const callback = (error, result) => { if (error !== null) { console.log(error) return } console.log(result) } try { calculateCube('hey', callback) } catch (e) { console.log(e) }
const calculateCube = (number, callback) => {
setTimeout(() => {
if (typeof number !== "number") {
callback(new TypeError("Numeric argument is expected"))
return
}
const cube = number * number * number
callback(null, cube)
}, 2000)
}
const callback = (error, result) => {
if (error !== null) {
console.log(error)
return
}
console.log(result)
}
try {
calculateCube('hey', callback)
} catch (e) {
console.log(e)
}

現在,控制檯的輸出將是:

帶有非法引數的TypeError示例

帶有非法引數的TypeError示例

這表明錯誤已得到適當處理。

處理Promise中的錯誤

大多數人傾向於使用Promise來處理非同步活動。Promise還有另一個優點——被拒絕的Promise不會終止你的指令碼。但是,您仍然需要實現一個catch塊來處理Promise中的錯誤。為了更好地理解這一點,讓我們使用Promises重寫calculateCube()函式:

Plain text
Copy to clipboard
Open code in new window
EnlighterJS 3 Syntax Highlighter
const delay = ms => new Promise(res => setTimeout(res, ms));
const calculateCube = async (number) => {
if (typeof number !== "number")
throw Error("Numeric argument is expected")
await delay(5000)
const cube = number * number * number
return cube
}
try {
calculateCube(4).then(r => console.log(r))
} catch (e) { console.log(e) }
const delay = ms => new Promise(res => setTimeout(res, ms)); const calculateCube = async (number) => { if (typeof number !== "number") throw Error("Numeric argument is expected") await delay(5000) const cube = number * number * number return cube } try { calculateCube(4).then(r => console.log(r)) } catch (e) { console.log(e) }
const delay = ms => new Promise(res => setTimeout(res, ms));
const calculateCube = async (number) => {
if (typeof number !== "number")
throw Error("Numeric argument is expected")
await delay(5000)
const cube = number * number * number
return cube
}
try {
calculateCube(4).then(r => console.log(r))
} catch (e) { console.log(e) }

前面程式碼中的超時已被隔離到delay函式中以便理解。如果您嘗試輸入一個字串而不是4,您獲得的輸出將類似於以下內容:

在Promise中帶有非法引數的TypeError示例

在Promise中帶有非法引數的TypeError示例

同樣,這是由於Promise在其他所有內容完成執行後引發錯誤。這個問題的解決方案很簡單。只需像這樣向Promise鏈新增呼叫catch()

Plain text
Copy to clipboard
Open code in new window
EnlighterJS 3 Syntax Highlighter
calculateCube("hey")
.then(r => console.log(r))
.catch(e => console.log(e))
calculateCube("hey") .then(r => console.log(r)) .catch(e => console.log(e))
calculateCube("hey")
.then(r => console.log(r))
.catch(e => console.log(e))

現在輸出將是:

處理帶有非法引數的TypeError示例

處理帶有非法引數的TypeError示例

您可以觀察到使用Promise處理錯誤是多麼容易。此外,您可以連結finally()塊和promise呼叫以新增將在錯誤處理完成後執行的程式碼。

或者,您也可以使用傳統的try-catch-finally技術來處理Promise中的錯誤。在這種情況下,您的promise呼叫如下所示:

Plain text
Copy to clipboard
Open code in new window
EnlighterJS 3 Syntax Highlighter
try {
let result = await calculateCube("hey")
console.log(result)
} catch (e) {
console.log(e)
} finally {
console.log('Finally executed")
}
try { let result = await calculateCube("hey") console.log(result) } catch (e) { console.log(e) } finally { console.log('Finally executed") }
try {
let result = await calculateCube("hey")
console.log(result)
} catch (e) {
console.log(e)
} finally {
console.log('Finally executed")
}

但是,這僅適用於非同步函式。因此,在Promise中處理錯誤的最優選方式是鏈式連線catchfinally連線到Promise呼叫。

throw/catch vs onerror() vs Callbacks vs Promises:哪種方法更佳?

有四種方法可供您使用,您必須知道如何在任何給定的用例中選擇最合適的方法。以下是您可以自己決定的方法:

throw/catch

您將在大多數情況下使用此方法。確保在你的catch塊中為所有可能的錯誤實現條件,如果你需要在try塊之後執行一些記憶體清理例程,請記住包含一個finally塊。

但是,太多的try/catch塊會使您的程式碼難以維護。如果您發現自己處於這種情況,您可能希望通過全域性處理程式或promise方法來處理錯誤。

在非同步try/catch塊和promise的catch()之間做出決定時,建議使用非同步try/catch塊,因為它們將使您的程式碼線性且易於除錯。

onerror()

當您知道您的應用程式必須處理許多錯誤並且它們可以很好地分散在整個程式碼庫中時,最好使用onerror()方法。onerror方法使您能夠處理錯誤,就好像它們只是您的應用程式處理的另一個事件一樣。您可以定義多個錯誤處理程式並在初始呈現時將它們附加到應用程式的視窗。

但是,您還必須記住,在錯誤範圍較小的較小專案中設定onerror()方法可能會帶來不必要的挑戰。如果您確定您的應用程式不會丟擲太多錯誤,那麼傳統的throw/catch方法將最適合您。

Callbacks and Promises

回撥和承諾中的錯誤處理因程式碼設計和結構而異。但是,如果您在編寫程式碼之前在這兩者之間進行選擇,最好使用Promise。

這是因為Promise具有用於連結catch()finally()塊以輕鬆處理錯誤的內建結構。這種方法比定義附加引數/重用現有引數來處理錯誤更容易和更清晰。

使用Git儲存庫跟蹤更改

由於程式碼庫中的手動錯誤,經常會出現許多錯誤。在開發或除錯程式碼時,您最終可能會進行不必要的更改,這可能會導致程式碼庫中出現新的錯誤。自動化測試是在每次更改後檢查程式碼的好方法。但是,它只能告訴您是否有問題。如果你不經常備份你的程式碼,你最終會浪費時間試圖修復一個以前執行良好的函式或指令碼。

這就是git發揮作用的地方。通過適當的提交策略,您可以使用您的git歷史作為備份系統來檢視您的程式碼在開發過程中的演變過程。您可以輕鬆地瀏覽舊提交併找出該函式的版本之前執行良好,但在不相關的更改後丟擲錯誤。

然後,您可以恢復舊程式碼或比較兩個版本以確定哪裡出了問題。現代Web開發工具(如GitHub Desktop或GitKraken)可幫助您並排視覺化這些更改並快速找出錯誤。

一個可以幫助您減少錯誤的習慣是 在您對程式碼進行重大更改時執行程式碼審查。如果您在一個團隊中工作,您可以建立一個拉取請求並讓團隊成員徹底審查它。這將幫助您使用第二雙眼睛來發現您可能遺漏的任何錯誤。

處理JavaScript錯誤的最佳實踐

上述方法足以幫助您為下一個JavaScript應用程式設計一個健壯的錯誤處理方法。但是,最好在實施時記住一些事情,以充分利用您的防錯功能。這裡有一些提示可以幫助您。

1. 處理操作異常時使用自定義錯誤

我們在本指南的前面介紹了自定義錯誤,讓您瞭解如何根據應用程式的獨特情況自定義錯誤處理。建議儘可能使用自定義錯誤而不是泛型Error類,因為它為呼叫環境提供了有關錯誤的更多上下文資訊。

最重要的是,自定義錯誤允許您調整錯誤在呼叫環境中的顯示方式。這意味著您可以根據需要選擇隱藏特定詳細資訊或顯示有關錯誤的其他資訊。

您可以根據需要格式化錯誤內容。這使您可以更好地控制錯誤的解釋和處理方式。

2.不要吞下任何例外

即使是最資深的開發人員也經常犯一個新手錯誤——在他們的程式碼中使用異常級別。

您可能會遇到有一段程式碼可以選擇執行的情況。如果它有效,那就太好了;如果沒有,你不需要做任何事情。

在這些情況下,通常很想將這段程式碼放在try塊中,並在其上附加一個空的catch塊。但是,通過這樣做,您將使那段程式碼保持開放狀態,從而導致任何型別的錯誤並逃脫懲罰。如果你有一個龐大的程式碼庫和許多這樣糟糕的錯誤管理結構的例項,這可能會變得很危險。

處理異常的最好方法是確定所有異常都將被處理的級別並將它們提高到那裡。此級別可以是控制器(在MVC架構應用程式中)或中介軟體(在傳統的面向伺服器的應用程式中)。

通過這種方式,您將瞭解在哪裡可以找到應用程式中發生的所有錯誤並選擇如何解決它們,即使這意味著不對它們做任何事情。

3. 對日誌和錯誤警報使用集中策略

記錄錯誤通常是處理錯誤的一個組成部分。那些未能制定集中策略來記錄錯誤的人可能會錯過有關其應用程式使用情況的寶貴資訊。

應用程式的事件日誌可以幫助您找出有關錯誤的關鍵資料並幫助快速除錯它們。如果您在應用程式中設定了適當的警報機制,您可以在錯誤到達大部分使用者群之前知道應用程式何時發生錯誤。

建議使用預先構建的記錄器或建立一個以滿足您的需求。您可以配置此記錄器以根據其級別(警告、除錯、資訊等)處理錯誤,並且一些記錄器甚至可以立即將日誌傳送到遠端記錄伺服器。通過這種方式,您可以觀察應用程式的邏輯在活動使用者中的執行情況。

4. 適當地通知使用者錯誤

在定義錯誤處理策略時要牢記的另一個要點是牢記使用者。

所有干擾您的應用程式正常執行的錯誤都必須向使用者顯示可見的警報,以通知他們出現問題,以便使用者可以嘗試制定解決方案。如果您知道錯誤的快速修復方法,例如重試操作或登出並重新登入,請務必在警報中提及它以幫助實時修復使用者體驗。

如果錯誤不會對日常使用者體驗造成任何干擾,您可以考慮抑制警報並將錯誤記錄到遠端伺服器以供以後解決。

5. 實現一箇中介軟體(Node.js)

Node.js環境支援中介軟體向伺服器應用程式新增功能。 您可以使用此功能為您的伺服器建立錯誤處理中介軟體。

使用中介軟體的最大好處是所有錯誤都集中在一個地方處理。您可以輕鬆選擇啟用/禁用此設定以進行測試。

以下是建立基本中介軟體的方法:

Plain text
Copy to clipboard
Open code in new window
EnlighterJS 3 Syntax Highlighter
const logError = err => {
console.log("ERROR: " + String(err))
}
const errorLoggerMiddleware = (err, req, res, next) => {
logError(err)
next(err)
}
const returnErrorMiddleware = (err, req, res, next) => {
res.status(err.statusCode || 500)
.send(err.message)
}
module.exports = {
logError,
errorLoggerMiddleware,
returnErrorMiddleware
}
const logError = err => { console.log("ERROR: " + String(err)) } const errorLoggerMiddleware = (err, req, res, next) => { logError(err) next(err) } const returnErrorMiddleware = (err, req, res, next) => { res.status(err.statusCode || 500) .send(err.message) } module.exports = { logError, errorLoggerMiddleware, returnErrorMiddleware }
const logError = err => {
console.log("ERROR: " + String(err))
}
const errorLoggerMiddleware = (err, req, res, next) => {
logError(err)
next(err)
}
const returnErrorMiddleware = (err, req, res, next) => {
res.status(err.statusCode || 500)
.send(err.message)
}
module.exports = {
logError,
errorLoggerMiddleware,
returnErrorMiddleware
}

然後,您可以在您的應用程式中使用此中介軟體,如下所示:

Plain text
Copy to clipboard
Open code in new window
EnlighterJS 3 Syntax Highlighter
const { errorLoggerMiddleware, returnErrorMiddleware } = require('./errorMiddleware')
app.use(errorLoggerMiddleware)
app.use(returnErrorMiddleware)
const { errorLoggerMiddleware, returnErrorMiddleware } = require('./errorMiddleware') app.use(errorLoggerMiddleware) app.use(returnErrorMiddleware)
const { errorLoggerMiddleware, returnErrorMiddleware } = require('./errorMiddleware')
app.use(errorLoggerMiddleware)
app.use(returnErrorMiddleware)

您現在可以在中介軟體中定義自定義邏輯以適當地處理錯誤。您不再需要擔心在整個程式碼庫中實現單個錯誤處理結構。

6. 重新啟動您的應用程式以處理程式設計師錯誤(Node.js)

當Node.js應用程式遇到程式設計師錯誤時,它們可能不一定會丟擲異常並嘗試關閉應用程式。此類錯誤可能包括由程式設計師錯誤引起的問題,例如高CPU消耗、記憶體膨脹或記憶體洩漏。處理這些問題的最佳方法是通過Node.js叢集模式或PM2等獨特工具使應用程式崩潰,從而優雅地重新啟動應用程式。這可以確保應用程式不會因使用者操作而崩潰,從而呈現糟糕的使用者體驗。

7. 捕獲所有未捕獲的異常(Node.js)

您永遠無法確定您已經涵蓋了您的應用程式中可能出現的所有錯誤。因此,實施備用策略以從您的應用程式中捕獲所有未捕獲的異常非常重要。

您可以這樣做:

Plain text
Copy to clipboard
Open code in new window
EnlighterJS 3 Syntax Highlighter
process.on('uncaughtException', error => {
console.log("ERROR: " + String(error))
// other handling mechanisms
})
process.on('uncaughtException', error => { console.log("ERROR: " + String(error)) // other handling mechanisms })
process.on('uncaughtException', error => {
console.log("ERROR: " + String(error))
// other handling mechanisms
})

您還可以確定發生的錯誤是標準異常還是自定義操作錯誤。根據結果​​,您可以退出程序並重新啟動它以避免意外行為。

8. 捕獲所有未處理的Promise Rejections (Node.js)

與您永遠無法涵蓋所有​​可能的異常類似,您很有可能會錯過處理所有可能的Promise Rejections。但是,與異常不同,Promise Rejection不會引發錯誤。

因此,一個重要的Promise Rejection可能會作為警告而溜走,並使您的應用程式面臨遇到意外行為的可能性。因此,實現一個回退機制來處理Promise Rejection是至關重要的。

您可以這樣做:

Plain text
Copy to clipboard
Open code in new window
EnlighterJS 3 Syntax Highlighter
const promiseRejectionCallback = error => {
console.log("PROMISE REJECTED: " + String(error))
}
process.on('unhandledRejection', callback)
const promiseRejectionCallback = error => { console.log("PROMISE REJECTED: " + String(error)) } process.on('unhandledRejection', callback)
const promiseRejectionCallback = error => {
console.log("PROMISE REJECTED: " + String(error))
}
process.on('unhandledRejection', callback)

小結

與任何其他程式語言一樣,JavaScript中的錯誤非常頻繁且自然。在某些情況下,您甚至可能需要故意丟擲錯誤以向使用者指示正確的響應。因此,瞭解它們的解剖結構和型別非常重要。

此外,您需要配備正確的工具和技術來識別和防止錯誤導致您的應用程式崩潰。

在大多數情況下,對於所有型別的JavaScript應用程式來說,通過仔細執行來處理錯誤的可靠策略就足夠了。

評論留言