整合營銷服務商

          電腦端+手機端+微信端=數據同步管理

          免費咨詢熱線:

          JavaScript錯誤處理完整指南

          JavaScript錯誤處理完整指南

          家好,我是 Echa。

          本文將帶你了解 JavaScript 中常見的錯誤類型,處理同步和異步 JavaScript/Node.js 代碼中錯誤和異常的方式,以及錯誤處理最佳實踐!

          1. 錯誤概述

          JavaScript 中的錯誤是一個對象,在發生錯誤時會拋出該對象以停止程序。在 JavaScript 中,可以通過構造函數來創建一個新的通用錯誤:

          const err=new Error("Error");
          

          當然,也可以省略 new 關鍵字:

          const err=Error("Error");
          

          Error 對象有三個屬性:

          • message:帶有錯誤消息的字符串;
          • name: 錯誤的類型;
          • stack:函數執行的堆棧跟蹤。

          例如,創建一個 TypeError 對象,該消息將攜帶實際的錯誤字符串,其 name 將是“TypeError”:

          const wrongType=TypeError("Expected number");
          
          wrongType.message; // 'Expected number'
          wrongType.name;    // 'TypeError'
          

          堆棧跟蹤是發生異常或警告等事件時程序所處的方法調用列表:

          它首先會打印錯誤名稱和消息,然后是被調用的方法列表。每個方法調用都說明其源代碼的位置和調用它的行。可以使用此數據來瀏覽代碼庫并確定導致錯誤的代碼段。此方法列表以堆疊的方式排列。它顯示了異常首先被拋出的位置以及它如何通過堆棧方法調用傳播。為異常實施捕獲不會讓它通過堆棧向上傳播并使程序崩潰。

          對于 Error 對象,Firefox 還實現了一些非標準屬性:

          • columnNumber:錯誤所在行的列號;
          • filename:發生錯誤的文件
          • lineNumber:發生錯誤的行號

          2. 錯誤類型

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

          JavaScript中的錯誤類型包括:

          • EvalError
          • InternalError
          • RangeError
          • ReferenceError
          • SyntaxError
          • TypeError
          • URIError

          這些錯誤類型都是實際的構造函數,旨在返回一個新的錯誤對象。最常見的就是 TypeError。大多數時候,大部分錯誤將直接來自 JavaScript 引擎,例如 InternalError 或 SyntaxError。

          JavaScript 提供了 instanceof 運算符可以用于區分異常類型:

          try {
            If (typeof x !==‘number’) {
                 throw new TypeError(‘x 應是數字’);
            } else if (x <=0) {
                 throw new RangeError('x 應大于 0');
            } else {
                 // ...
            }
          } catch (err) {
              if (err instanceof TypeError) {
                // 處理 TypeError 錯誤
              } else if (err instanceof RangeError) {
                // 處理 RangeError 錯誤
            } else {
                // 處理其他類型錯誤
            }
          }
          

          下面來了解 JavaScript 中最常見的錯誤類型,并了解它們發生的時間和原因。

          (1)SyntaxError

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

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

          • 缺少引號
          • 缺少右括號
          • 大括號或其他字符對齊不當

          (2)TypeError

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

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

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

          (3)ReferenceError

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

          ReferenceErrors 發生的一些常見原因如下:

          • 在變量名中輸入錯誤。
          • 試圖訪問其作用域之外的塊作用域變量。
          • 在加載之前從外部庫引用全局變量。

          (4)RangeError

          RangeError 表示范圍錯誤。當變量設置的值超出其合法值范圍時,將拋出 RangeError。它通常發生在將值作為參數傳遞給函數時,并且給定值不在函數參數的范圍內。當使用記錄不完整的第三方庫時,有時修復起來會很棘手,因為需要知道參數的可能值范圍才能傳遞正確的值。

          RangeError 發生的一些常見場景如下:

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

          (5)URIError

          URIError 表示 URI錯誤。當 URI 的編碼和解碼出現問題時,會拋出 URIError。JavaScript 中的 URI 操作函數包括:decodeURIdecodeURIComponent 等。如果使用了錯誤的參數(無效字符),就會拋出 URIError。

          (6)EvalError

          EvalError 表示 Eval 錯誤。當 eval() 函數調用發生錯誤時,會拋出 EvalError。不過,當前的 JavaScript 引擎或 ECMAScript 規范不再拋出此錯誤。但是,為了向后兼容,它仍然是存在的。

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

          (7)InternalError

          InternalError 表示內部錯誤。在 JavaScript 運行時引擎發生異常時使用。它表示代碼可能存在問題也可能不存在問題。

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

          • 當 JavaScript 運行時的補丁或更新帶有引發異常的錯誤時(這種情況很少發生);
          • 當代碼包含對于 JavaScript 引擎而言太大的實體時(例如,數組初始值設定項太大、遞歸太多)。

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

          注意: 現代 JavaScript 中不會拋出 EvalError 和 InternalError。

          (8)創建自定義錯誤類型

          雖然 JavaScript 提供了足夠的錯誤類型類列表來涵蓋大多數情況,但如果這些錯誤類型不能滿足要求,還可以創建新的錯誤類型。這種靈活性的基礎在于 JavaScript 允許使用 throw 命令拋出任何內容。

          可以通過擴展 Error 類以創建自定義錯誤類:

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

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

          throw ValidationError("未找到該屬性: name")
          

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

          try {
              validateForm() // 拋出 ValidationError 的代碼
          } catch (e) {
              if (e instanceof ValidationError) {
                
              }
              else {
                
              }
          }
          

          3. 拋出錯誤

          很多人認為錯誤和異常是一回事。實際上,Error 對象只有在被拋出時才會成為異常

          在 JavaScript 中拋出異常,可以使用 throw 來拋出 Error 對象:

          throw TypeError("Expected number");
          

          或者:

          throw new TypeError("Expected number");
          

          來看一個簡單的例子:

          function toUppercase(string) {
            if (typeof string !=="string") {
              throw TypeError("Expected string");
            }
          
            return string.toUpperCase();
          }
          

          在這里,我們檢查函數參數是否為字符串。如果不是,就拋出異常。

          從技術上講,我們可以在 JavaScript 中拋出任何東西,而不僅僅是 Error 對象:

          throw Symbol();
          throw 33;
          throw "Error!";
          throw null;
          

          但是,最好避免這樣做:要拋出正確的 Error 對象,而不是原語

          4. 拋出異常時會發生什么?

          異常一旦拋出,就會在程序堆棧中冒泡,除非在某個地方被捕獲。

          來看下面的例子:

          function toUppercase(string) {
            if (typeof string !=="string") {
              throw TypeError("Expected string");
            }
          
            return string.toUpperCase();
          }
          
          toUppercase(4);
          

          在瀏覽器或 Node.js 中運行此代碼,程序將停止并拋出錯誤:

          這里還顯示了發生錯誤的確切行。這個錯誤就是一個堆棧跟蹤,有助于跟蹤代碼中的問題。堆棧跟蹤從下到上:

          at toUppercase (<anonymous>:3:11)
          at <anonymous>:9:1
          

          toUppercase 函數在第 9 行調用,在第 3 行拋出錯誤。除了在瀏覽器的控制臺中查看此堆棧跟蹤之外,還可以在 Error 對象的 stack 屬性上訪問它。

          介紹完這些關于錯誤的基礎知識之后,下面來看看同步和異步 JavaScript 代碼中的錯誤和異常處理。

          5. 同步錯誤處理

          (1)常規函數的錯誤處理

          同步代碼會按照代碼編寫順序執行。讓我們再看看前面的例子:

          function toUppercase(string) {
            if (typeof string !=="string") {
              throw TypeError("Expected string");
            }
          
            return string.toUpperCase();
          }
          
          toUppercase(4);
          

          在這里,引擎調用并執行 toUppercase,這一切都是同步發生的。 要捕獲由此類同步函數引發的異常,可以使用 try/catch/finally:

          try {
            toUppercase(4);
          } catch (error) {
            console.error(error.message);
          } finally {
            // ...
          }
          

          通常,try 會處理正常的路徑,或者可能進行的函數調用。catch 就會捕獲實際的異常,它接收 Error 對象。而不管函數的結果如何,finally 語句都會運行:無論它失敗還是成功,finally 中的代碼都會運行。

          (2)生成器函數的錯誤處理

          JavaScript 中的生成器函數是一種特殊類型的函數。它可以隨意暫停和恢復,除了在其內部范圍和消費者之間提供雙向通信通道。為了創建一個生成器函數,需要在 function 關鍵字后面加上一個 *

          function* generate() {
          //
          }
          

          只要進入函數,就可以使用 yield 來返回值:

          function* generate() {
            yield 33;
            yield 99;
          }
          

          生成器函數的返回值是一個迭代器對象。要從生成器中提取值,可以使用兩種方法:

          • 在迭代器對象上調用 next()
          • 使用 for...of 進行迭代

          以上面的代碼為例,要從生成器中獲取值,可以這樣做:

          function* generate() {
            yield 33;
            yield 99;
          }
          
          const go=generate();
          

          當我們調用生成器函數時,這里的 go 就是生成的迭代器對象。接下來,就可以調用 go.next() 來繼續執行:

          function* generate() {
            yield 33;
            yield 99;
          }
          
          const go=generate();
          
          const firstStep=go.next().value; // 33
          const secondStep=go.next().value; // 99
          

          生成器也可以接受來自調用者的值和異常。除了 next(),從生成器返回的迭代器對象還有一個 throw() 方法。使用這種方法,就可以通過向生成器中注入異常來停止程序:

          function* generate() {
            yield 33;
            yield 99;
          }
          
          const go=generate();
          
          const firstStep=go.next().value; // 33
          
          go.throw(Error("Tired of iterating!"));
          
          const secondStep=go.next().value; // never reached
          

          要捕獲此類錯誤,可以使用 try/catch 將代碼包裝在生成器中:

          function* generate() {
            try {
              yield 33;
              yield 99;
            } catch (error) {
              console.error(error.message);
            }
          }
          

          生成器函數也可以向外部拋出異常。 捕獲這些異常的機制與捕獲同步異常的機制相同:try/catch/finally。

          下面是使用 for...of 從外部使用的生成器函數的示例:

          function* generate() {
            yield 33;
            yield 99;
            throw Error("Tired of iterating!");
          }
          
          try {
            for (const value of generate()) {
              console.log(value);
            }
          } catch (error) {
            console.error(error.message);
          }
          

          輸出結果如下:

          這里,try 塊中包含正常的迭代。如果發生任何異常,就會用 catch 捕獲它。

          6. 異步錯誤處理

          瀏覽器中的異步包括定時器、事件、Promise 等。異步世界中的錯誤處理與同步世界中的處理不同。下面來看一些例子。

          (1)定時器的錯誤處理

          上面我們介紹了如何使用 try/catch/finally 來處理錯誤,那異步中可以使用這些來處理錯誤嗎?先來看一個例子:

          function failAfterOneSecond() {
            setTimeout(()=> {
              throw Error("Wrong!");
            }, 1000);
          }
          

          此函數在大約 1 秒后會拋出錯誤。那處理此異常的正確方法是什么?以下代碼是無效的:

          function failAfterOneSecond() {
            setTimeout(()=> {
              throw Error("Wrong!");
            }, 1000);
          }
          
          try {
            failAfterOneSecond();
          } catch (error) {
            console.error(error.message);
          }
          

          我們知道,try/catch是同步的,所以沒辦法這樣來處理異步中的錯誤。當傳遞給 setTimeout的回調運行時,try/catch 早已執行完畢。程序將會崩潰,因為未能捕獲異常。它們是在兩條路徑上執行的:

          A: --> try/catch
          B: --> setTimeout --> callback --> throw
          

          (2)事件的錯誤處理

          我們可以監聽頁面中任何 HTML 元素的事件,DOM 事件的錯誤處理機制遵循與任何異步 Web API 相同的方案。

          來看下面的例子:

          const button=document.querySelector("button");
          
          button.addEventListener("click", function() {
            throw Error("error");
          });
          

          這里,在單擊按鈕后立即拋出了異常,我們該如何捕獲這個異常呢?這樣寫是不起作用的,也不會阻止程序崩潰:

          const button=document.querySelector("button");
          
          try {
            button.addEventListener("click", function() {
              throw Error("error");
            });
          } catch (error) {
            console.error(error.message);
          }
          

          與前面的 setTimeout 例子一樣,任何傳遞給 addEventListener 的回調都是異步執行的:

          Track A: --> try/catch
          Track B: --> addEventListener --> callback --> throw
          

          如果不想讓程序崩潰,為了正確處理錯誤,就必須將 try/catch 放到 addEventListener 的回調中。不過這樣做并不是最佳的處理方式,與 setTimeout 一樣,異步代碼路徑拋出的異常無法從外部捕獲,并且會使程序崩潰。

          下面會介紹 Promises 和 async/await 是如何簡化異步代碼的錯誤處理的。

          (3)onerror

          HTML 元素有許多事件處理程序,例如 onclickonmouseenteronchange 等。除此之外,還有 onerror,每當 <img> 標簽或 <script> 等 HTML 元素命中不存在的資源時,onerror 事件處理程序就會觸發。

          來看下面的例子:

          <body>
            <img src="nowhere-to-be-found.png">
          </body>
          

          當訪問的資源缺失時,瀏覽器的控制臺就會報錯:

          GET http://localhost:5000/nowhere-to-be-found.png
          [HTTP/1.1 404 Not Found 3ms]
          

          在 JavaScript 中,可以使用適當的事件處理程序“捕獲”此錯誤:

          const image=document.querySelector("img");
          
          image.onerror=function(event) {
            console.log(event);
          };
          

          或者使用 addEventListener 來監聽 error 事件,當發生錯誤時進行處理:

          const image=document.querySelector("img");
          
          image.addEventListener("error", function(event) {
            console.log(event);
          });
          
          

          此模式對于加載備用資源以代替丟失的圖像或腳本很有用。不過需要記住:onerror 與 throw 或 try/catch 是無關的。

          (4)Promise 的錯誤處理

          下面來通過最上面的 toUppercase 例子看看 Promise 是如何處理錯誤的:

          function toUppercase(string) {
            if (typeof string !=="string") {
              throw TypeError("Expected string");
            }
          
            return string.toUpperCase();
          }
          
          toUppercase(4);
          

          對上面的代碼進行修改,不返回簡單的字符串或異常,而是分別使用 Promise.rejectPromise.resolve 來處理錯誤和成功:

          function toUppercase(string) {
            if (typeof string !=="string") {
              return Promise.reject(TypeError("Expected string"));
            }
          
            const result=string.toUpperCase();
          
            return Promise.resolve(result);
          }
          

          從技術上講,這段代碼中沒有任何異步的內容,但它可以很好地說明 Promise 的錯誤處理機制。

          現在我們就可以在 then 中使用結果,并使用 catch 來處理被拒絕的 Promise:

          toUppercase(99)
            .then(result=> result)
            .catch(error=> console.error(error.message));
          

          輸出結果如下:

          在 Promise 中,catch 是用來處理錯誤的。除了 catch 還有 finally,類似于 try/catch 中的finally。不管 Promise 結果如何,finally 都會執行:

          toUppercase(99)
            .then(result=> result)
            .catch(error=> console.error(error.message))
            .finally(()=> console.log("Finally"));
          

          輸出結果如下:

          需要記住,任何傳遞給 then/catch/finally 的回調都是由微任務隊列異步處理的。 它們是微任務,優先于事件和計時器等宏任務。

          (5)Promise, error, throw

          作為拒絕 Promise 時的最佳實踐,可以傳入 error 對象:

          Promise.reject(TypeError("Expected string"));
          

          這樣,在整個代碼庫中保持錯誤處理的一致性。 其他團隊成員總是可以訪問 error.message,更重要的是可以檢查堆棧跟蹤。

          除了 Promise.reject 之外,還可以通過拋出異常來退出 Promise 執行鏈。來看下面的例子:

          Promise.resolve("A string").then(value=> {
            if (typeof value==="string") {
              throw TypeError("Expected number!");
            }
          });
          

          這里使用 字符串來 resolve 一個 Promise,然后執行鏈立即使用 throw 斷開。為了停止異常的傳播,可以使用 catch 來捕獲錯誤:

          Promise.resolve("A string")
            .then(value=> {
              if (typeof value==="string") {
                throw TypeError("Expected number!");
              }
            })
            .catch(reason=> console.log(reason.message));
          

          這種模式在 fetch 中很常見,可以通過檢查 response 對象來查找錯誤:

          fetch("https://example-dev/api/")
            .then(response=> {
              if (!response.ok) {
                throw Error(response.statusText);
              }
          
              return response.json();
            })
            .then(json=> console.log(json));
          

          這里的異常可以使用 catch 來攔截。 如果失敗了,并且沒有攔截它,異常就會在堆棧中向上冒泡。這本身并沒有什么問題,但不同的環境對未捕獲的拒絕有不同的反應。

          例如,Node.js 會讓任何未處理 Promise 拒絕的程序崩潰:

          DeprecationWarning: Unhandled promise rejections are deprecated. In the future, promise rejections that are not handled will terminate the Node.js process with a non-zero exit code.
          

          所以,最好去捕獲錯誤。

          (6)使用 Promise 處理定時器錯誤

          對于計時器或事件,不能捕獲回調拋出的異常。上面有一個例子:

          function failAfterOneSecond() {
            setTimeout(()=> {
              throw Error("Error");
            }, 1000);
          }
          
          // 不生效
          try {
            failAfterOneSecond();
          } catch (error) {
            console.error(error.message);
          }
          

          我們可以使用 Promise 來包裝計時器:

          function failAfterOneSecond() {
            return new Promise((_, reject)=> {
              setTimeout(()=> {
                reject(Error("Error"));
              }, 1000);
            });
          }
          

          這里通過 reject 捕獲了一個 Promise 拒絕,它帶有一個 error 對象。此時就可以用 catch 來處理異常了:

          failAfterOneSecond().catch(reason=> console.error(reason.message));
          

          這里使用 value 作為 Promise 的返回值,使用 reason 作為拒絕的返回對象。

          (7)Promise.all 的錯誤處理

          Promise.all 方法接受一個 Promise 數組,并返回所有解析 Promise 的結果數組:

          const promise1=Promise.resolve("one");
          const promise2=Promise.resolve("two");
          
          Promise.all([promise1, promise2]).then((results)=> console.log(results));
          
          // 結果: ['one', 'two']
          

          如果這些 Promise 中的任何一個被拒絕,Promise.all 將拒絕并返回第一個被拒絕的 Promise 的錯誤。

          為了在 Promise.all 中處理這些情況,可以使用 catch:

          const promise1=Promise.resolve("good");
          const promise2=Promise.reject(Error("Bad"));
          const promise3=Promise.reject(Error("Bad+"));
          
          Promise.all([promise1, promise2, promise3])
            .then(results=> console.log(results))
            .catch(error=> console.error(error.message));
          

          如果想要運行一個函數而不考慮 Promise.all 的結果,可以使用 finally:

          Promise.all([promise1, promise2, promise3])
            .then(results=> console.log(results))
            .catch(error=> console.error(error.message))
            .finally(()=> console.log("Finally"));
          

          (8)Promise.any 的錯誤處理

          Promise.any 和 Promise.all 恰恰相反。Promise.all 如果某一個失敗,就會拋出第一個失敗的錯誤。而 Promise.any 總是返回第一個成功的 Promise,無論是否發生任何拒絕。

          相反,如果傳遞給 Promise.any 的所有 Promise 都被拒絕,那產生的錯誤就是 AggregateError。 來看下面的例子:

          const promise1=Promise.reject(Error("Error"));
          const promise2=Promise.reject(Error("Error+"));
          
          Promise.any([promise1, promise2])
            .then(result=> console.log(result))
            .catch(error=> console.error(error))
            .finally(()=> console.log("Finally"));
          

          輸出結果如下:

          這里用 catch 處理錯誤。AggregateError 對象具有與基本錯誤相同的屬性,外加一個 errors 屬性:

          const promise1=Promise.reject(Error("Error"));
          const promise2=Promise.reject(Error("Error+"));
          
          Promise.any([promise1, promise2])
            .then(result=> console.log(result))
            .catch(error=> console.error(error.errors))
            .finally(()=> console.log("Finally"));
          

          此屬性是一個包含所有被拒絕的錯誤信息的數組:

          (9)Promise.race 的錯誤處理

          Promise.race 接受一個 Promise 數組,并返回第一個成功的 Promise 的結果:

          const promise1=Promise.resolve("one");
          const promise2=Promise.resolve("two");
          
          Promise.race([promise1, promise2]).then(result=> 
            console.log(result)
          );
          
          // 結果:one
          

          那如果有被拒絕的 Promise,但它不是傳入數組中的第一個呢:

          const promise1=Promise.resolve("one");
          const rejection=Promise.reject(Error("Bad"));
          const promise2=Promise.resolve("two");
          
          Promise.race([promise1, rejection, promise2]).then(result=>
            console.log(result)
          );
          
          // 結果:one
          

          這樣結果還是 one,不會影響正常的執行。

          如果被拒絕的 Promise 是數組的第一個元素,則 Promise.race 拒絕,就必須要必須捕獲拒絕:

          const promise1=Promise.resolve("one");
          const rejection=Promise.reject(Error("Bad"));
          const promise2=Promise.resolve("two");
          
          Promise.race([rejection, promise1, promise2])
            .then(result=> console.log(result))
            .catch(error=> console.error(error.message));
          
          // Bad
          

          (10)Promise.allSettled 的錯誤處理

          Promise.allSettled 是 ECMAScript 2020 新增的 API。它和 Promise.all 類似,不過不會被短路,也就是說當Promise全部處理完成后,可以拿到每個 Promise 的狀態, 而不管其是否處理成功。

          來看下面的例子:

          const promise1=Promise.resolve("Good!");
          const promise2=Promise.reject(Error("Bad!"));
          
          Promise.allSettled([promise1, promise2])
            .then(results=> console.log(results))
            .catch(error=> console.error(error))
            .finally(()=> console.log("Finally"));
          

          這里向 Promise.allSettled 傳遞了一個包含兩個 Promise 的數組:一個已解決,另一個已拒絕。

          輸出結果如下:

          (11)async/await 的錯誤處理

          JavaScript 中的 async/await 表示異步函數,用同步的方式去編寫異步,可讀性更好。

          下面來改編上面的同步函數 toUppercase,通過將 async 放在 function 關鍵字之前將其轉換為異步函數:

          async function toUppercase(string) {
            if (typeof string !=="string") {
              throw TypeError("Expected string");
            }
          
            return string.toUpperCase();
          }
          

          只需在 function 前加上 async 前綴,就可以讓函數返回一個 Promise。這意味著我們可以在函數調用之后鏈式調用 then、catch 和 finally:

          toUppercase("hello")
            .then(result=> console.log(result))
            .catch(error=> console.error(error.message))
            .finally(()=> console.log("Always runs!"));
          

          當從 async 函數中拋出異常時,異常會成為底層 Promise 被拒絕的原因。任何錯誤都可以從外部用 catch 攔截。

          除此之外,還可以使用 try/catch/finally 來處理錯誤,就像在同步函數中一樣。

          例如,從另一個函數 consumer 中調用 toUppercase,它方便地用 try/catch/finally 包裝了函數調用:

          async function toUppercase(string) {
            if (typeof string !=="string") {
              throw TypeError("Expected string");
            }
          
            return string.toUpperCase();
          }
          
          async function consumer() {
            try {
              await toUppercase(98);
            } catch (error) {
              console.error(error.message);
            } finally {
              console.log("Finally");
            }
          }
          
          consumer();
          

          輸出結果如下:

          (12)異步生成器的錯誤處理

          JavaScript 中的異步生成器是能夠生成 Promise 而不是簡單值的生成器函數。它將生成器函數與異步相結合,結果是一個生成器函數,其迭代器對象向消費者公開一個 Promise。

          要創建一個異步生成器,需要聲明一個帶有星號 * 的生成器函數,前綴為 async:

          async function* asyncGenerator() {
            yield 33;
            yield 99;
            throw Error("Bad!"); // Promise.reject
          }
          

          因為異步生成器是基于 Promise,所以同樣適用 Promise 的錯誤處理規則,在異步生成器中,throw 會導致 Promise 拒絕,可以用 catch 攔截它。

          要想從異步生成器處理 Promise,可以使用 then:

          const go=asyncGenerator();
          
          go.next().then(value=> console.log(value));
          go.next().then(value=> console.log(value));
          go.next().catch(reason=> console.error(reason.message));
          

          輸出結果如下:

          也使用異步迭代 for await...of。 要使用異步迭代,需要用 async 函數包裝 consumer:

          async function* asyncGenerator() {
            yield 33;
            yield 99;
            throw Error("Bad"); // Promise.reject
          }
          
          async function consumer() {
            for await (const value of asyncGenerator()) {
              console.log(value);
            }
          }
          
          consumer();
          

          與 async/await 一樣,可以使用 try/catch 來處理任何異常:

          async function* asyncGenerator() {
            yield 33;
            yield 99;
            throw Error("Bad"); // Promise.reject
          }
          
          async function consumer() {
            try {
              for await (const value of asyncGenerator()) {
                console.log(value);
              }
            } catch (error) {
              console.error(error.message);
            }
          }
          
          consumer();
          

          輸出結果如下:

          從異步生成器函數返回的迭代器對象也有一個 throw() 方法。在這里對迭代器對象調用 throw() 不會拋出異常,而是 Promise 拒絕:

          async function* asyncGenerator() {
            yield 33;
            yield 99;
            yield 11;
          }
          
          const go=asyncGenerator();
          
          go.next().then(value=> console.log(value));
          go.next().then(value=> console.log(value));
          
          go.throw(Error("Reject!"));
          
          go.next().then(value=> console.log(value)); 
          

          輸出結果如下:

          可以通過以下方式來捕獲錯誤:

          go.throw(Error("Let's reject!")).catch(reason=>
            console.error(reason.message)
          );
          

          我們知道,迭代器對象的 throw() 是在生成器內部發送異常的。所以還可以使用以下方式來處理錯誤:

          async function* asyncGenerator() {
            try {
              yield 33;
              yield 99;
              yield 11;
            } catch (error) {
              console.error(error.message);
            }
          }
          
          const go=asyncGenerator();
          
          go.next().then(value=> console.log(value));
          go.next().then(value=> console.log(value));
          
          go.throw(Error("Reject!"));
          
          go.next().then(value=> console.log(value));
          

          5. Node.js 錯誤處理

          (1)同步錯誤處理

          Node.js 中的同步錯誤處理與 JavaScript 是一樣的,可以使用 try/catch/finally。

          (2)異步錯誤處理:回調模式

          對于異步代碼,Node.js 強烈依賴兩個術語:

          • 事件發射器
          • 回調模式

          在回調模式中,異步 Node.js API 接受一個函數,該函數通過事件循環處理并在調用堆棧為空時立即執行。

          來看下面的例子:

          const { readFile }=require("fs");
          
          function readDataset(path) {
            readFile(path, { encoding: "utf8" }, function(error, data) {
              if (error) console.error(error);
              // data操作
            });
          }
          

          這里可以看到回調中錯誤處理:

          function(error, data) {
              if (error) console.error(error);
              // data操作
          }
          

          如果使用 fs.readFile 讀取給定路徑時出現任何錯誤,我們都會得到一個 error 對象。這時我們可以:

          • 單地記錄錯誤對象。
          • 拋出異常。
          • 將錯誤傳遞給另一個回調。

          要想拋出異常,可以這樣做:

          const { readFile }=require("fs");
          
          function readDataset(path) {
            readFile(path, { encoding: "utf8" }, function(error, data) {
              if (error) throw Error(error.message);
              // data操作
            });
          }
          

          但是,與 DOM 中的事件和計時器一樣,這個異常會使程序崩潰。 使用 try/catch 停止它的嘗試將不起作用:

          const { readFile }=require("fs");
          
          function readDataset(path) {
            readFile(path, { encoding: "utf8" }, function(error, data) {
              if (error) throw Error(error.message);
              // data操作
            });
          }
          
          try {
            readDataset("not-here.txt");
          } catch (error) {
            console.error(error.message);
          }
          

          如果不想讓程序崩潰,可以將錯誤傳遞給另一個回調:

          const { readFile }=require("fs");
          
          function readDataset(path) {
            readFile(path, { encoding: "utf8" }, function(error, data) {
              if (error) return errorHandler(error);
              // data操作
            });
          }
          

          這里的 errorHandler 是一個簡單的錯誤處理函數:

          function errorHandler(error) {
            console.error(error.message);
            // 處理錯誤:寫入日志、發送到外部logger
          }
          

          (3)異步錯誤處理:事件發射器

          Node.js 中的大部分工作都是基于事件的。大多數時候,我們會與發射器對象和一些偵聽消息的觀察者進行交互。

          Node.js 中的任何事件驅動模塊(例如 net)都擴展了一個名為 EventEmitter 的根類。EventEmitter 有兩個基本方法:on 和 emit。

          下面來看一個簡單的 HTTP 服務器:

          const net=require("net");
          
          const server=net.createServer().listen(8081, "127.0.0.1");
          
          server.on("listening", function () {
            console.log("Server listening!");
          });
          
          server.on("connection", function (socket) {
            console.log("Client connected!");
            socket.end("Hello client!");
          });
          

          這里我們監聽了兩個事件:listening 和 connection。除了這些事件之外,事件發射器還公開一個錯誤事件,在出現錯誤時觸發。

          如果這段代碼監聽的端口是 80,就會得到一個異常:

          const net=require("net");
          
          const server=net.createServer().listen(80, "127.0.0.1");
          
          server.on("listening", function () {
            console.log("Server listening!");
          });
          
          server.on("connection", function (socket) {
            console.log("Client connected!");
            socket.end("Hello client!");
          });
          

          輸出結果如下:

          events.js:291
                throw er;
                ^
          
          Error: listen EACCES: permission denied 127.0.0.1:80
          Emitted 'error' event on Server instance at: ...
          

          為了捕獲它,可以為 error 注冊一個事件處理函數:

          server.on("error", function(error) {
            console.error(error.message);
          });
          

          這樣就會輸出:

          listen EACCES: permission denied 127.0.0.1:80
          

          6. 錯誤處理最佳實踐

          最后,我們來看看處理 JavaScript 異常的最佳實踐!

          (1)不要過度處理錯誤

          錯處理的第一個最佳實踐就是不要過度使用“錯誤處理”。通常,我們會在外層處理錯誤,從內層拋出錯誤,這樣一旦出現錯誤,就可以更好地理解是什么原因導致的。

          然而,開發人員常犯的錯誤之一是過度使用錯誤處理。有時這樣做是為了讓代碼在不同的文件和方法中看起來保持一致。但是,不幸的是,這些會對應用程序和錯誤檢測造成不利影響。

          因此,只關注代碼中可能導致錯誤的地方,錯誤處理將有助于提高代碼健壯性并增加檢測到錯誤的機會。

          (2)避免瀏覽器特定的非標準方法

          盡管許多瀏覽器都遵循一個通用標準,但某些特定于瀏覽器的 JavaScript 實現在其他瀏覽器上卻失敗了。例如,以下語法僅適用于 Firefox:

          catch(e) { 
            console.error(e.filename + ': ' + e.lineNumber); 
          }
          

          因此,在處理錯誤時,盡可能使用跨瀏覽器友好的 JavaScript 代碼。

          (3)遠程錯誤記錄

          當發生錯誤時,我們應該得到通知以了解出了什么問題。這就是錯誤日志的用武之地。JavaScript 代碼是在用戶的瀏覽器中執行的。因此,需要一種機制來跟蹤客戶端瀏覽器中的這些錯誤,并將它們發送到服務器進行分析。

          可以嘗試使用以下工具來監控并上報錯誤:

          • Sentry(https://sentry.io/): 專注于異常(應用崩潰)而不是信息錯誤。它提供了應用中錯誤的完整概述,包括受影響的用戶數量、調用堆棧、受影響的瀏覽器以及導致錯誤的提交等詳細信息。
          • Rollbar(https://rollbar.com/): 用于前端、后端和移動應用的無代理錯誤監控工具。它提供人工智能輔助的工作流程,使開發人員能夠在錯誤影響用戶之前立即采取行動。它會顯示受錯誤影響的客戶數量、受影響的平臺或瀏覽器的類型以及之前是否發生過類似錯誤或是否已經存在解決方案等數據。

          (4)錯誤處理中間件(Node.js)

          Node.js 環境支持使用中間件向服務端應用中添加功能。因此可以創建一個錯誤處理中間件。使用中間件的最大好處是所有錯誤都在一個地方集中處理。可以選擇啟用/禁用此設置以輕松進行測試。

          以下是創建基本中間件的方法:

          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)
          

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

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

          我們可能永遠無法涵蓋應用中可能發生的所有錯誤。因此,必須實施回退策略以捕獲應用中所有未捕獲的異常。

          可以這樣做:

          process.on('uncaughtException', error=> {
              console.log("ERROR: " + String(error))
              // 其他處理機制
          })
          

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

          (6)捕獲所有未處理的 Promise 拒絕(Node.js)

          與異常不同的是,promise 拒絕不會拋出錯誤。因此,一個被拒絕的 promise 可能只是一個警告,這讓應用有可能遇到意外行為。因此,實現處理 promise 拒絕的回退機制至關重要。

          可以這樣做:

          const promiseRejectionCallback=error=> {
              console.log("PROMISE REJECTED: " + String(error))
          }
          
          process.on('unhandledRejection', callback)
          

          參考文章

          • https://www.valentinog.com/blog/error/
          • https://kinsta.com/blog/errors-in-javascript/
          • https://blog.bitsrc.io/javascript-exception-handling-patterns-best-practices-f7d6fcab735d

          做一道網絡攻防平臺的試題中遇到一個需要用Quoted-Printable編碼解碼的內容,網上答案基本都是其它語言寫的,看不懂,分享一個JS(JavaScript)代碼的,經過我的測試可以使用,而且可以在瀏覽器控制臺中使用,非常方便。

          function qpDecode(qpString) {

          var decodedString='';

          for (var i=0; i < qpString.length; i++) {

          if (qpString[i]==='=') {

          var hexChar=qpString.substr(i + 1, 2);

          if (/^[0-9A-Fa-f]{2}$/.test(hexChar)) {

          decodedString +=String.fromCharCode(parseInt(hexChar, 16));

          i +=2;

          } else {

          decodedString +=qpString[i];

          }

          } else {

          decodedString +=qpString[i];

          }

          }

          return decodedString;

          }

          // 使用示例

          var encodedString="Hello=20World!";

          console.log(qpDecode(encodedString)); // 輸出: "Hello World!"

          其功能是從一個采用quoted-printable編碼格式的字符串(qpString)中解碼并返回原始文本。URL編碼是一種用于將非ASCII字符或特殊字符轉換為可以在URL中傳輸的格式的方法。Quoted-Printable編碼是一種特殊的URL編碼,用于將非ASCII字符轉換為可以在文本中安全傳輸的格式。在Quoted-Printable編碼中,非ASCII字符或某些特殊字符(例如?&=等)會被轉換為=HH的格式,其中HH表示一個或兩個十六進制數字。

          自程序員之手的 JavaScript 代碼,該如何變成計算機所能理解的機器語言呢?本文將帶你走進 JavaScript 引擎內部,一探究竟。

          作者 | Lydia Hallie

          譯者 | 彎月,責編 | 屠敏

          以下為譯文:

          JavaScript 很酷(這一點不用我說),但一臺機器究竟是怎樣理解我們編寫的代碼呢?作為JavaScript 開發者,我們通常不需要處理編譯器的東西。但是,了解 JavaScript 引擎的基礎知識,知道它如何將人類能看懂的JS代碼變成機器能理解的東西,是絕對是有好處的!

          注意:這篇文章主要根據 Node.js 和基于 Chromium 的瀏覽器使用的V8引擎撰寫。

          當HTML解析器遇到代碼中的script標簽時,就會從網絡、緩存或者已安裝的service worker里加載源代碼。這一步的結果就是腳本內容,以字節流的形式返回,這個字節流需要解碼器來處理!字節流解碼器會在字節流下載的時候進行解碼。

          字節流解碼器根據流中的字節數據來創建符號(token)。例如,0066解碼成f,0075解碼成u,006e解碼成n,0063解碼成c,0074解碼成t,0069解碼成i,006f解碼成o,006e解碼成n,然后是一個空格。似乎你寫了一個function!這是JavaScript的保留關鍵字,因此就會創建一個符號,然后發給解析器(以及預解析器,我的GIF圖里沒有說,但我會稍后解釋)。字節流中的其余內容也會類似處理。

          引擎有兩個解析器:一個是預解析器(pre-parser),另一個是解析器(parser)。預解析器只負責盡早檢查符號,找出其中的語法錯誤。這樣可以減少在代碼中發現錯誤所需的時間。否則這些錯誤就要由解析器負責發現了!

          如果沒有錯誤,解析器就會根據它從字節流解碼器收到的符號創建節點,然后使用這些節點創建一顆抽象語法樹,簡稱AST。

          接下來就是解釋器(interpreter)出場了!解釋器會遍歷整個AST,根據AST的內容生成字節碼。字節碼生成完成后,就會刪除AST以釋放更多的內存。這樣就得到了機器能夠運行的代碼!

          雖然字節碼很快,但它還可以更快。字節碼在運行的時候會生成信息。它可以檢測到哪些行為會更頻繁發生,哪些類型的數據會更經常被使用。如果某個函數被調用了許多次,那么就可以通過優化加快速度!

          字節碼會連同生成的類型反饋一起發送到優化編譯器(optimizing compiler)。優化編譯器會處理負責處理字節碼和類型反饋,然后生成高度優化過的機器碼。

          JavaScript 是一個動態類型語言,這意味著數據類型經常會變化。如果 JavaScript 引擎每次都必須檢查值的類型,那就會非常慢。

          然而,JavaScript 的引擎使用了一種叫做內聯緩存(inline caching)的方法。它會在內存中緩存代碼,期待著以后會用同樣的行為返回同樣的值!比如,一個函數被調用100次,到目前為止每次都返回同樣的值。那么引擎就會假設該函數在第101次調用時依然會返回同樣的值。

          我們假設有一個函數sum,到目前為止每次調用都使用兩個數值作為參數:

          上面的調用會返回3!下次被調用時,引擎就會假設我們依然會用兩個數值進行調用。

          如果這個假設正確,那就不需要進行動態查找,可以直接使用內存中保存的值。否則,如果假設錯誤,就會進行反優化,將代碼從優化過的機器碼恢復成原始的字節碼。

          例如,假設下次調用時傳遞了一個字符串而不是數值。由于 JavaScript 是動態類型,這樣做不會產生任何錯誤!

          這意味著數字2會被強制轉換成字符串,然后函數會返回字符串"12"。因此引擎會去執行字節碼,然后更新類型反饋。

          希望這篇文章對你有幫助性!當然,引擎還有許多其他方面我沒有討論到(如JS heap,call stack等),也許以后會討論!如果你對JavaScript的內部原理有興趣,我強烈建議你自己做一些研究,V8是開源的,關于其工作原理的文檔也非常好!

          原文:https://dev.to/lydiahallie/javascript-visualized-the-javascript-engine-4cdf

          本文為 CSDN 翻譯,轉載請注明來源出處。

          【End】

          【成就一億技術人】各位擼代碼的大佬們,CSDN重磅推出升級版原力計劃。只要是原創,只要你首發,現在,都擁有上首頁的幸運可能。我們衷心地希望,這一代程序員網紅就是你,詳情戳海報~~


          主站蜘蛛池模板: 69久久精品无码一区二区| 九九无码人妻一区二区三区| 久久精品无码一区二区三区日韩| 国产成人精品一区二三区| 国产成人欧美一区二区三区| 好爽毛片一区二区三区四无码三飞| 精品国产一区二区麻豆| 精品一区二区三区无码视频| 国产在线观看一区精品| 亚洲天堂一区在线| 国产一区中文字幕| 一区高清大胆人体| 成人免费视频一区| 国产免费一区二区视频| 国产精品一区视频| 国产MD视频一区二区三区| 国模少妇一区二区三区| 国精产品999一区二区三区有限 | 国产成人无码AV一区二区| 亚州日本乱码一区二区三区| 国产精品无码一区二区在线| 国产亚洲一区区二区在线| 亚洲熟女综合一区二区三区| 国产福利电影一区二区三区,亚洲国模精品一区 | 日韩精品无码一区二区中文字幕| 亚洲欧美日韩一区二区三区| 亚洲国产一区明星换脸| 97久久精品无码一区二区| www一区二区三区| 无码人妻一区二区三区免费手机| 一区二区三区在线观看视频| tom影院亚洲国产一区二区 | 日韩精品一区二区三区老鸭窝| 武侠古典一区二区三区中文| 麻豆一区二区三区精品视频| 久久精品人妻一区二区三区| 日韩高清一区二区三区不卡| 一区二区三区视频在线观看| 亚洲福利视频一区二区| 亚洲一区二区三区成人网站| 国产凹凸在线一区二区|