處理 JavaScript 錯誤的權威指南

已發表: 2022-01-24

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

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

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

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

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

事不宜遲,讓我們開始吧!

什麼是 JavaScript 錯誤?

編程錯誤是指程序無法正常運行的情況。 當程序不知道如何處理手頭的工作時,可能會發生這種情況,例如嘗試打開不存在的文件或在沒有網絡連接的情況下訪問基於 Web 的 API 端點時。

這些情況促使程序向用戶拋出錯誤,說明它不知道如何繼續。 該程序收集盡可能多的有關錯誤的信息,然後報告它無法繼續前進。

墨菲定律指出,任何可能出錯的事情最終都會出錯 這在 JavaScript 的世界中應用得太好了 準備好本指南點擊推

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

JavaScript 中的錯誤是在發生編程錯誤時顯示的對象。 這些對象包含有關錯誤類型、導致錯誤的語句以及發生錯誤時的堆棧跟踪的大量信息。 JavaScript 還允許程序員創建自定義錯誤,以便在調試問題時提供額外信息。

錯誤的屬性

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

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

  1. message : 攜帶錯誤信息的字符串值
  2. name :發生的錯誤類型(我們將在下一節深入探討)
  3. stack :發生錯誤時執行的代碼的堆棧跟踪。

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

了解堆棧跟踪

堆棧跟踪是發生異常或警告等事件時程序所在的方法調用列表。 這是伴隨異常的示例堆棧跟踪的樣子:

錯誤“TypeError: Numeric argument is expected”顯示在灰色背景上,並帶有其他堆棧詳細信息。
堆棧跟踪示例。

如您所見,它首先打印錯誤名稱和消息,然後是被調用的方法列表。 每個方法調用都說明其源代碼的位置以及調用它的行。 您可以使用這些數據瀏覽您的代碼庫並確定是哪段代碼導致了錯誤。

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

錯誤與異常

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

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

 const wrongTypeError = TypeError("Wrong type found, expected character")

這就是wrongTypeError對像變成異常的方式:

 throw wrongTypeError

然而,大多數人傾向於使用在拋出錯誤對象時定義錯誤對象的簡寫形式:

 throw TypeError("Wrong type found, expected character")

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

JavaScript 中的錯誤類型

JavaScript 中有一系列預定義的錯誤類型。 只要程序員沒有明確處理應用程序中的錯誤,它們就會由 JavaScript 運行時自動選擇和定義。

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

範圍錯誤

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

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

  • 試圖通過 Array 構造函數創建一個非法長度的數組。
  • 將錯誤值傳遞給數字方法,例如toExponential()toPrecision()toFixed()等。
  • 將非法值傳遞給像normalize()這樣的字符串函數。

參考錯誤

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

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

  • 在變量名中打錯字。
  • 試圖訪問其範圍之外的塊範圍變量。
  • 在加載之前從外部庫(如來自 jQuery 的 $)中引用全局變量。

語法錯誤

這些錯誤是最容易修復的錯誤之一,因為它們表明代碼語法有錯誤。 由於 JavaScript 是一種解釋而不是編譯的腳本語言,因此當應用程序執行包含錯誤的腳本時會拋出這些腳本語言。 在編譯語言的情況下,在編譯過程中會識別出此類錯誤。 因此,在修復之前不會創建應用程序二進製文件。

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

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

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

類型錯誤

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

  • 調用不是方法的對象。
  • 試圖訪問空對像或未定義對象的屬性
  • 將字符串視為數字,反之亦然

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

內部錯誤

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

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

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

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

URI錯誤

當非法使用decodeURIComponent等全局 URI 處理函數時,會發生 URIError。 它通常表示傳遞給方法調用的參數不符合 URI 標準,因此沒有被方法正確解析。

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

評估錯誤

eval()函數調用發生錯誤時,會發生 EvalError。 eval()函數用於執行存儲在字符串中的 JavaScript 代碼。 然而,由於安全問題,我們不鼓勵使用eval()函數,並且當前的 ECMAScript 規範不再拋出EvalError類,這種錯誤類型的存在只是為了保持與舊版 JavaScript 代碼的向後兼容性。

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

創建自定義錯誤類型

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

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

 throw 8 throw "An error occurred"

但是,拋出原始數據類型不會提供有關錯誤的詳細信息,例如其類型、名稱或隨附的堆棧跟踪。 為了解決這個問題並標準化錯誤處理過程,我們提供了Error類。 也不鼓勵在拋出異常時使用原始數據類型。

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

 class ValidationError extends Error { constructor(message) { super(message); this.name = "ValidationError"; } }

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

 throw ValidationError("Property not found: name")

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

 try { validateForm() // code that throws a ValidationError } catch (e) { if (e instanceof ValidationError) // do something else // do something else }

JavaScript 中最常見的 10 個錯誤

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

1. 未捕獲的 RangeError

在幾種不同的情況下,Google Chrome 中會出現此錯誤。 首先,如果您調用遞歸函數並且它不會終止,則可能會發生這種情況。 您可以在 Chrome 開發者控制台中自行查看:

錯誤“Uncaught RangeError: Maximum call stack size exceeded”顯示在紅色背景上,旁邊是一個紅色十字圖標,上面有一個遞歸函數的代碼。
帶有遞歸函數調用的 RangeError 示例。

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

錯誤“Uncaught RangeError: toExponential() argument must be between 0 and 100”顯示在紅色背景上,旁邊是一個紅色的十字圖標,上面有一個 toExponential() 函數調用。
帶有 toExponential() 調用的 RangeError 示例。

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

num = 4. num.toExponential(2)。輸出:4.00e+0。
toExponential() 函數調用的輸出。

2. Uncaught TypeError: Cannot set property

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

 var list list.count = 0

這是您將收到的輸出:

錯誤“Uncaught TypeError: Cannot set properties of undefined”顯示在紅色背景上,旁邊是一個紅色十字圖標,上面有一個 list.count = 0 賦值。
類型錯誤示例。

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

在使用 {} 初始化列表後設置 list.count = 10,因此輸出為 10。
如何修復類型錯誤。

3. Uncaught TypeError: Cannot read property

這是 JavaScript 中最常出現的錯誤之一。 當您嘗試讀取屬性或調用未定義對象的函數時,會發生此錯誤。 您可以通過在 Chrome 開發人員控制台中運行以下代碼來非常輕鬆地重現它:

 var func func.call()

這是輸出:

錯誤“Uncaught TypeError: Cannot read properties of undefined”顯示在紅色背景上,紅色十字圖標旁邊帶有 func.call()。
帶有未定義函數的 TypeError 示例。

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

 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 堆棧跟踪。

這是因為,在渲染時,狀態容器是未定義的; 因此,上面不存在任何屬性items 。 修復這個錯誤很容易。 您只需要為狀態容器提供初始默認值。

 // ... const [state, setState] = useState({items: []}); // ...

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

一個項目符號列表,其中包含兩個項目,分別為“卡 1”和“卡 2”。
代碼輸出。

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

4. TypeError: 'undefined' is not an object

當您嘗試訪問未定義對象的屬性或調用未定義對象的方法時,會在 Safari 中發生此錯誤。 您可以從上面運行相同的代碼來自己重現錯誤。

錯誤“TypeError: undefined is not an object”顯示在紅色感嘆號圖標旁邊的紅色背景上,上面有 func.call() 方法調用。
帶有未定義函數的 TypeError 示例。

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

5. TypeError: null is not an object

這又與前面的錯誤相似。 它發生在 Safari 上,這兩個錯誤之間的唯一區別是,當正在訪問其屬性或方法的對象為null而不是undefined時,會拋出這個錯誤。 您可以通過運行以下代碼來重現這一點:

 var func = null func.call()

這是您將收到的輸出:

“TypeError: null is not an object”錯誤消息,顯示在紅色感嘆號圖標旁邊的紅色背景上。
帶有 null 函數的 TypeError 示例。

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

6. TypeError: 無法讀取屬性'length'

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

錯誤“Uncaught TypeError: Cannot read property 'length' of undefined”顯示在紅色背景上的紅色十字圖標旁邊,上面有 myButton.length 調用。
帶有未定義對象的 TypeError 示例。

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

紅色背景上顯示的錯誤“未捕獲的類型錯誤:無法讀取未定義的屬性”,旁邊是紅色十字圖標,上面有 myButton.length 調用。
在較新的 Chrome 版本上帶有未定義對象的 TypeError 示例。

再次,修復是確保您嘗試訪問其長度的對象存在並且未設置為null

7. TypeError: 'undefined' is not a function

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

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

 function showAlert() { alert("message here") } document.addEventListener("click", () => { this.showAlert(); })

如果你執行上面的代碼,它會拋出我們討論過的錯誤。 這是因為作為事件偵聽器傳遞的匿名函數正在document的上下文中執行。

相反,函數showAlert是在window的上下文中定義的。

要解決這個問題,您必須通過使用bind()方法綁定函數來傳遞對函數的正確引用:

 document.addEventListener("click", this.showAlert.bind(this))

8. ReferenceError: event is not defined

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

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

9. TypeError:賦值給常量變量

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

錯誤“Uncaught TypeError: Assignment to constant variable”顯示在紅色背景上的紅色十字圖標旁邊,上面有 func = 6 賦值。
帶有常量對象分配的 TypeError 示例。

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

10.(未知):腳本錯誤

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

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

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

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

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

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

手動拋出和捕獲錯誤

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

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

  • 你可以throw任何東西,包括數字、字符串和Error對象。
  • 但是,不建議拋出諸如字符串和數字之類的原始數據類型,因為它們不攜帶有關錯誤的調試信息。
  • 示例: throw TypeError("Please provide a string")

嘗試

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

 try { // error-prone code here }

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

抓住

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

 catch (exception) { // code to handle the exception here }

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

 try { // business logic code } catch (exception) { // error handling code }

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

 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 語句來處理所有可能的錯誤情況。 它看起來像這樣:

 try { // business logic code } catch (exception) { if (exception instanceof TypeError) { // do something } else if (exception instanceof RangeError) { // do something else } }

最後

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

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

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

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

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

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

 const image = document.querySelector("img") image.onerror = (event) => { console.log("Error occurred: " + event) }

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

 window.onerror = (event) => { console.log("Error occurred: " + event) }

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

通過回調傳遞錯誤

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

考慮以下代碼:

需要一個可以為您提供競爭優勢的託管解決方案? Kinsta 為您提供令人難以置信的速度、最先進的安全性和自動縮放功能。 查看我們的計劃

const calculateCube = (number, callback) => { setTimeout(() => { const cube = number * number * number callback(cube) }, 1000) } const callback = result => console.log(result) calculateCube(4, callback)

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

如果您嘗試在函數調用中輸入一個字符串而不是 4,您將得到NaN作為結果。

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

 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 塊後拋出錯誤。

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

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

 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: Numeric argument is expected”。
帶有非法參數的 TypeError 示例。

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

處理 Promise 中的錯誤

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

 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在其他所有內容完成執行後拋出錯誤。 這個問題的解決方案很簡單。 只需像這樣向 Promise 鏈添加一個catch()調用:

 calculateCube("hey") .then(r => console.log(r)) .catch(e => console.log(e))

現在輸出將是:

深灰色背景上顯示消息“錯誤:需要數字參數”。
處理帶有非法參數的 TypeError 示例。

您可以觀察到使用 Promise 處理錯誤是多麼容易。 此外,您可以鏈接finally()塊和 promise 調用以添加將在錯誤處理完成後運行的代碼。

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

 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:哪個是最好的?

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

扔/接

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

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

在異步 try/catch 塊和 promise 的catch()之間做出決定時,建議使用異步 try/catch 塊,因為它們將使您的代碼線性且易於調試。

錯誤()

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

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

回調和承諾

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

這是因為 Promise 有一個內置結構,用於鏈接catch()finally()塊來輕鬆處理錯誤。 這種方法比定義附加參數/重用現有參數來處理錯誤更容易和更清晰。

使用 Git 存儲庫跟踪更改

由於代碼庫中的手動錯誤,經常會出現許多錯誤。 While developing or debugging your code, you might end up making unnecessary changes that may cause new errors to appear in your codebase. Automated testing is a great way to keep your code in check after every change. However, it can only tell you if something's wrong. If you don't take frequent backups of your code, you'll end up wasting time trying to fix a function or a script that was working just fine before.

This is where git plays its role. With a proper commit strategy, you can use your git history as a backup system to view your code as it evolved through the development. You can easily browse through your older commits and find out the version of the function working fine before but throwing errors after an unrelated change.

You can then restore the old code or compare the two versions to determine what went wrong. Modern web development tools like GitHub Desktop or GitKraken help you to visualize these changes side by side and figure out the mistakes quickly.

A habit that can help you make fewer errors is running code reviews whenever you make a significant change to your code. If you're working in a team, you can create a pull request and have a team member review it thoroughly. This will help you use a second pair of eyes to spot out any errors that might have slipped by you.

Best Practices for Handling Errors in JavaScript

The above-mentioned methods are adequate to help you design a robust error handling approach for your next JavaScript application. However, it would be best to keep a few things in mind while implementing them to get the best out of your error-proofing. Here are some tips to help you.

1. Use Custom Errors When Handling Operational Exceptions

We introduced custom errors early in this guide to give you an idea of how to customize the error handling to your application's unique case. It's advisable to use custom errors wherever possible instead of the generic Error class as it provides more contextual information to the calling environment about the error.

On top of that, custom errors allow you to moderate how an error is displayed to the calling environment. This means that you can choose to hide specific details or display additional information about the error as and when you wish.

You can go so far as to format the error contents according to your needs. This gives you better control over how the error is interpreted and handled.

2. Do Not Swallow Any Exceptions

Even the most senior developers often make a rookie mistake — consuming exceptions levels deep down in their code.

You might come across situations where you have a piece of code that is optional to run. If it works, great; if it doesn't, you don't need to do anything about it.

In these cases, it's often tempting to put this code in a try block and attach an empty catch block to it. However, by doing this, you'll leave that piece of code open to causing any kind of error and getting away with it. This can become dangerous if you have a large codebase and many instances of such poor error management constructs.

The best way to handle exceptions is to determine a level on which all of them will be dealt and raise them until there. This level can be a controller (in an MVC architecture app) or a middleware (in a traditional server-oriented app).

This way, you'll get to know where you can find all the errors occurring in your app and choose how to resolve them, even if it means not doing anything about them.

3. Use a Centralized Strategy for Logs and Error Alerts

Logging an error is often an integral part of handling it. Those who fail to develop a centralized strategy for logging errors may miss out on valuable information about their app's usage.

An app's event logs can help you figure out crucial data about errors and help to debug them quickly. If you have proper alerting mechanisms set up in your app, you can know when an error occurs in your app before it reaches a large section of your user base.

It's advisable to use a pre-built logger or create one to suit your needs. You can configure this logger to handle errors based on their levels (warning, debug, info, etc.), and some loggers even go so far as to send logs to remote logging servers immediately. This way, you can watch how your application's logic performs with active users.

4. Notify Users About Errors Appropriately

Another good point to keep in mind while defining your error handling strategy is to keep the user in mind.

All errors that interfere with the normal functioning of your app must present a visible alert to the user to notify them that something went wrong so the user can try to work out a solution. If you know a quick fix for the error, such as retrying an operation or logging out and logging back in, make sure to mention it in the alert to help fix the user experience in real-time.

In the case of errors that don't cause any interference with the everyday user experience, you can consider suppressing the alert and logging the error to a remote server for resolving later.

5. Implement a Middleware (Node.js)

The Node.js environment supports middlewares to add functionalities to server applications. You can use this feature to create an error-handling middleware for your server.

The most significant benefit of using middleware is that all of your errors are handled centrally in one place. You can choose to enable/disable this setup for testing purposes easily.

Here's how you can create a basic middleware:

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 { errorLoggerMiddleware, returnErrorMiddleware } = require('./errorMiddleware') app.use(errorLoggerMiddleware) app.use(returnErrorMiddleware)

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

6. 重新啟動您的應用程序以處理程序員錯誤(Node.js)

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

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

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

您可以這樣做:

 process.on('uncaughtException', error => { console.log("ERROR: " + String(error)) // other handling mechanisms })

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

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

與您永遠無法涵蓋所有可能的異常類似,您很有可能會錯過處理所有可能的承諾拒絕。 但是,與異常不同,promise 拒絕不會引發錯誤。

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

您可以這樣做:

 const promiseRejectionCallback = error => { console.log("PROMISE REJECTED: " + String(error)) } process.on('unhandledRejection', callback)
如果您創建一個應用程序,您也有可能在其中創建錯誤和其他問題。 在本指南的幫助下了解如何處理它們️ 點擊推

概括

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

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

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

是否還有其他您無法解決的 JavaScript 錯誤? 有什麼建設性地處理 JS 錯誤的技術嗎? 在下面的評論中讓我們知道!