精品欧美一区二区三区在线观看 _久久久久国色av免费观看性色_国产精品久久在线观看_亚洲第一综合网站_91精品又粗又猛又爽_小泽玛利亚一区二区免费_91亚洲精品国偷拍自产在线观看 _久久精品视频在线播放_美女精品久久久_欧美日韩国产成人在线

面試率超高的JS錯誤處理,看這篇就夠了!

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

本文將帶你了解 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 操作函數包括:decodeURI、decodeURIComponent 等。如果使用了錯誤的參數(無效字符),就會拋出 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 元素有許多事件處理程序,例如 onclick、onmouseenter、onchange 等。除此之外,還有 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.reject 和 Promise.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)

責任編輯:武曉燕 來源: 前端充電寶
相關推薦

2022-10-17 09:01:09

JavaScripNode.js

2019-08-16 09:41:56

UDP協議TCP

2021-09-30 07:59:06

zookeeper一致性算法CAP

2022-03-29 08:23:56

項目數據SIEM

2021-05-07 07:52:51

Java并發編程

2022-05-27 08:18:00

HashMapHash哈希表

2023-12-07 09:07:58

2022-08-18 20:45:30

HTTP協議數據

2021-12-13 10:43:45

HashMapJava集合容器

2024-08-27 11:00:56

單例池緩存bean

2017-03-30 22:41:55

虛擬化操作系統軟件

2023-09-25 08:32:03

Redis數據結構

2023-10-04 00:32:01

數據結構Redis

2021-07-28 13:29:57

大數據PandasCSV

2021-09-10 13:06:45

HDFS底層Hadoop

2023-11-07 07:46:02

GatewayKubernetes

2023-11-03 08:53:15

StrconvGolang

2021-04-11 08:30:40

VRAR虛擬現實技術

2021-10-21 06:52:17

ZooKeeper分布式配置

2021-11-10 07:47:48

Traefik邊緣網關
點贊
收藏

51CTO技術棧公眾號

日韩视频一区二区三区四区| 色三级在线观看| 欧美日韩亚洲一区| 日韩av中文字幕在线| 无码无遮挡又大又爽又黄的视频| 91电影在线播放| 国产成人午夜视频| 国产成人午夜视频网址| 精品国产欧美日韩不卡在线观看| 大桥未久女教师av一区二区| 色av一区二区| 可以在线看黄的网站| 日本中文字幕电影在线观看 | www黄色在线| 快射视频在线观看| 91免费观看视频在线| 91精品视频在线播放| 美日韩一二三区| 一区二区三区四区电影| 亚洲欧美色图片| 韩国三级丰满少妇高潮| 激情开心成人网| 亚洲成人av一区| 伊人婷婷久久| 偷拍自拍在线视频| 国产成人免费在线观看不卡| 日韩av大片免费看| 国产精品自拍视频一区| 99久久99久久精品国产片果冰| 日韩av一区二区在线| 一级片黄色免费| 国精品产品一区| 日韩欧美大尺度| 精品国产av无码一区二区三区| www在线播放| 久久亚洲免费视频| 国产精品乱子乱xxxx| 国产毛片毛片毛片毛片| 日韩av网站免费在线| 国内精品久久久久久久| 久久高清内射无套| 视频在线不卡免费观看| 一区二区三区国产在线观看| 亚洲调教欧美在线| 国产欧美自拍一区| 亚洲成av人影院在线观看| 天天看片天天操| 高清在线一区| 欧美日精品一区视频| 免费看黄色一级大片| 成人午夜视屏| 欧美日韩亚洲激情| 国产特级黄色大片| 午夜裸体女人视频网站在线观看| 亚洲成人www| 国产 日韩 欧美在线| 午夜av在线免费观看| 亚洲天堂2016| 日韩欧美一级在线| 欧美黑人猛交的在线视频| 亚洲欧美日韩国产中文在线| 黄色免费高清视频| 久久99精品久久久久久野外| 国产精品国产a| 亚洲精品国产精品国自产| a√资源在线| 国产精品美日韩| 日日噜噜噜夜夜爽爽| 欧洲日本在线| 亚洲激情网站免费观看| 国内自拍中文字幕| 国产美女精品写真福利视频| 精品久久久一区| 黄色av网址在线播放| 亚洲最大网站| 欧美人牲a欧美精品| 亚洲视频一二三四| 2021年精品国产福利在线| 欧美精品一区二区久久久| 少妇精品一区二区| 精品av一区二区| 久久视频免费在线播放| 免费看一级一片| 久久久人人人| 国产热re99久久6国产精品| 国产农村妇女毛片精品| 成人美女在线视频| 日本中文不卡| av文字幕在线观看| 欧美日韩国产丝袜另类| 亚洲不卡视频在线| 日韩免费高清视频网站| 亚洲精选在线观看| 国产精品免费在线视频| 国产精品v亚洲精品v日韩精品 | 国产成人精品免费视频网站| 国产九区一区在线| 国产毛片在线| 亚洲一区二区四区蜜桃| 97av视频在线观看| 99精品国产九九国产精品| 亚洲第一男人av| 91香蕉视频污在线观看| 亚洲精品国产日韩| 国产精品视频自拍| 色欲av永久无码精品无码蜜桃 | 日本一区二区三区视频免费看| 婷婷在线视频观看| 精品国产福利在线| 久久久九九九热| 国产真实有声精品录音| 九九视频直播综合网| 欧美成人精品网站| 99久久精品国产导航| 一区二区三区四区不卡| 亚洲国产成人二区| 精品国产亚洲在线| 国精产品一区一区二区三区mba| 日韩五码在线| 91成人免费视频| 92国产在线视频| 午夜久久久久久| 黑人性生活视频| 操欧美老女人| 人人做人人澡人人爽欧美| 国产浮力第一页| 国产精品嫩草99a| 成人在线观看黄| 日韩精品亚洲aⅴ在线影院| 美日韩精品免费视频| 中文字幕免费在线看| 久久伊人中文字幕| 国产青青在线视频| 成人在线视频你懂的| 成年无码av片在线| 国产精品嫩草影院桃色| 国产精品久久夜| 免费激情视频在线观看| 蜜桃一区二区| 欧美一性一乱一交一视频| 亚洲国产精品国自产拍久久| 亚洲欧美日韩小说| 欧美视频国产视频| 99久久.com| 91久久久久久| 99热国产在线中文| 555夜色666亚洲国产免| 黑人操日本美女| 极品美女销魂一区二区三区免费| 亚洲精品国产精品国自产观看| 成人自拍视频网| 一本色道久久88精品综合| 亚洲中文一区二区| 国产欧美日韩在线视频| 搡女人真爽免费午夜网站| 欧美裸体在线版观看完整版| 国产精品户外野外| aⅴ在线视频男人的天堂| 欧美日韩国产不卡| frxxee中国xxx麻豆hd| 国产精品一级黄| 成人免费网站入口| 日韩三级毛片| 国产99视频在线观看| av在线免费播放网站| 欧美视频一二三区| 成人免费视频国产免费观看| 国产精品一区在线观看乱码| 欧美视频在线第一页| 国产精品毛片视频| 日本精品久久久久久久| www在线免费观看| 日韩一区二区三区四区| 不卡的免费av| 久久久噜噜噜久噜久久综合| 久久久久久久久久福利| 色喇叭免费久久综合网| 亚洲综合大片69999| av免费不卡国产观看| 国产午夜精品一区理论片飘花| 中文字幕黄色av| 亚洲美女精品一区| 精品中文字幕在线播放| 蜜桃久久av一区| 日本黄大片在线观看| 成人精品毛片| 国产免费亚洲高清| 免费电影视频在线看| 亚洲毛片在线看| 92久久精品一区二区| 亚洲二区在线观看| 女性裸体视频网站| 成人丝袜视频网| 国产小视频精品| 国产精品大片免费观看| 明星裸体视频一区二区| 国产精品一站二站| 欧美性在线视频| 二区三区四区高清视频在线观看| 日韩经典中文字幕| 国产精品亚洲lv粉色| 欧美视频一区二区三区…| 婷婷伊人五月天| 国产午夜亚洲精品不卡| 性一交一黄一片| 日韩va欧美va亚洲va久久| 国内少妇毛片视频| 日韩一区二区三区免费播放| 国内外成人免费视频| 日韩欧美激情| 国产精品扒开腿做| а√天堂8资源中文在线| 色妞欧美日韩在线| 免费黄网站在线观看| 欧美va天堂va视频va在线| 中文字幕人成人乱码亚洲电影| 亚洲一二三区视频在线观看| 免费一级suv好看的国产网站| 91在线视频网址| 无码人妻一区二区三区在线视频| 日韩精品乱码av一区二区| 国产日韩av网站| 午夜天堂精品久久久久| 亚洲欧美国产不卡| 国产欧美日韩影院| 精品视频免费观看| 51亚洲精品| 91视频婷婷| 国产午夜久久av| 国产精品中文字幕在线| 天然素人一区二区视频| 欧美一级高清免费| 九色porny自拍视频在线观看| 欧美成人四级hd版| 超碰在线观看免费版| www.欧美精品| 调教视频免费在线观看| 中文字幕日韩精品在线观看| 极品白浆推特女神在线观看| 日韩精品免费观看| 午夜福利理论片在线观看| 精品久久久久久久久久久久久久久久久 | 日韩电影免费网站| 视频在线99| 日韩久久综合| 一区二区三区不卡在线| 日韩成人三级| 伊人情人网综合| 久久精品久久久| 黄黄视频在线观看| 影音先锋日韩在线| 成人av在线播放观看| 激情综合在线| 国产黄页在线观看| 国产日韩一区二区三区在线| 欧美亚洲另类色图| 欧美亚洲自偷自偷| 日韩欧美xxxx| 久久超碰97人人做人人爱| 伊人成人222| 国产精品影视在线| 欧美丰满熟妇bbb久久久| 成人精品亚洲人成在线| 亚洲精品乱码久久久久久不卡| 91亚洲永久精品| 中国女人特级毛片| 亚洲人成精品久久久久久| 免费在线观看一级片| 亚洲一区二区五区| 91丝袜一区二区三区| 在线免费亚洲电影| 国产日韩欧美视频在线观看| 日韩免费观看高清完整版| 天堂网在线观看视频| 在线观看日韩专区| 1区2区3区在线视频| 97成人超碰免| 成人a在线观看高清电影| 亚洲影影院av| 校花撩起jk露出白色内裤国产精品| 日本精品一区二区三区不卡无字幕| 日本电影一区二区| 欧美精品在欧美一区二区| 久久性色av| 在线观看中文av| 久久噜噜亚洲综合| 99久久久免费精品| 精品久久久精品| 国产毛片一区二区三区va在线| 亚洲国产一区自拍| 日本免费视频在线观看| 97视频免费在线看| 日日夜夜亚洲| 久久久久久久免费| 在线成人直播| 欧美一级黄色片视频| 国产精品一区在线| 国产成人免费观看网站| 亚洲一区二区高清| 在线观看中文字幕2021| 亚洲国产中文字幕在线观看 | 精品少妇v888av| 中文在线免费二区三区| 91九色露脸| 日韩精品看片| 国产精品秘入口18禁麻豆免会员| 韩国成人福利片在线播放| 黄色在线观看av| 亚洲一区在线视频| 国产精品高潮呻吟AV无码| 亚洲欧美日韩国产中文| 日韩精品卡一| 91精品久久久久久久久久入口| 婷婷精品视频| www.国产在线播放| 国产一区二区三区视频在线播放| 最近中文字幕免费视频| 亚洲国产一二三| www.com欧美| 久久精品99久久久香蕉| 78精品国产综合久久香蕉| 久久国产精品 国产精品| 韩国精品一区二区三区| 99日在线视频| 中文av一区二区| www.国产毛片| 日韩电影中文字幕在线观看| 国产丝袜在线播放| 亚洲自拍偷拍色图| 国产精品成人一区二区不卡| 欧美伦理视频在线观看| 久久影院视频免费| 免费av网站在线| 国产视频自拍一区| 美女高潮在线观看| 国内精品久久国产| 99国产成+人+综合+亚洲欧美| 亚洲av无一区二区三区久久| 亚洲日本欧美天堂| 国产成人免费看一级大黄| 久久香蕉国产线看观看网| 台湾天天综合人成在线| 中文视频一区视频二区视频三区| 蜜臀av性久久久久蜜臀aⅴ | 成人三级视频| 亚洲免费看av| 中文字幕中文字幕一区| 91成品人影院| 久久国产精品电影| 国产精品99久久免费| 一级全黄肉体裸体全过程| 久久99国产精品免费| 久久福利免费视频| 日韩一区二区在线免费观看| 亚洲综合影视| av一区二区三区免费| 国自产拍偷拍福利精品免费一 | 亚洲av无码乱码国产麻豆| 欧美日韩xxxxx| 高清精品xnxxcom| 人妻精品无码一区二区三区| 久久亚洲一区二区三区明星换脸| 69亚洲精品久久久蜜桃小说| 这里只有精品在线播放| 日本成人一区二区| 成人午夜免费剧场| 粉嫩久久99精品久久久久久夜| 久久精品无码人妻| 精品无码久久久久久国产| 91九色综合| 强伦女教师2:伦理在线观看| 国产高清一区日本| 国产成人自拍视频在线| 亚洲夜晚福利在线观看| 色综合一区二区日本韩国亚洲 | 99在线视频影院| 日本不卡二区| 国产精品一区在线观看乱码 | 青草视频在线免费直播| 国产一区二区三区四区hd| 久久亚洲一区| 永久久久久久久| 日韩高清免费观看| 秋霞国产精品| 黄色网在线视频| 92国产精品观看| 国产精品高潮呻吟av| 69视频在线免费观看| 欧美gay男男猛男无套| 精品1卡二卡三卡四卡老狼| 色综合久久久久综合体| av在线免费网址| 欧美一区免费视频| 国产高清久久久久| 最新中文字幕在线观看视频| 欧美精品手机在线| 精品午夜久久| 91精品又粗又猛又爽| 欧美日韩高清一区二区不卡| 国产精选在线|