JavaScript 錯誤處理大全

編程中有什麼錯誤?

在咱們的程序中,事情並不是一路順風javascript

特別是在某些狀況下,咱們可能但願在中止程序或在發生不良情況時通知用戶
例如:html

  • 程序試圖打開一個不存在的文件。
  • 網絡鏈接斷開。
  • 用戶進行了無效的輸入。

在全部的這些狀況下,咱們做爲程序員都會產生錯誤,或者讓編程引擎爲咱們建立一些錯誤。前端

在建立錯誤以後,咱們能夠向用戶通知消息,或者能夠徹底中止執行。java

JavaScript 中有什麼錯誤?

JavaScript 中的錯誤是一個對象,隨後被拋出,用以終止程序。node

要在 JavaScript 中建立新錯誤,咱們調用相應的構造函數。例如,要建立一個新的通用錯誤,能夠執行如下操做:程序員

const err = new Error("Something bad happened!");

建立錯誤對象時,也能夠省略關鍵字 new面試

const err = Error("Something bad happened!");

建立後,錯誤對象將顯示三個屬性:編程

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

例如,若是咱們用適當的消息建立一個新的 TypeError 對象,則 message 將攜帶實際的錯誤字符串,而 name 則爲 TypeErrorjson

const wrongType = TypeError("Wrong type given, expected number");

wrongType.message; // "Wrong type given, expected number"
wrongType.name; // "TypeError"

Firefox 還實現了一堆非標準屬性,例如 columnNumberfilenamelineNumbersegmentfault

JavaScript 中的錯誤類型

JavaScript 中有不少類型的錯誤,即:

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

請記住,全部這些錯誤類型都是實際構造函數,旨在返回一個新的錯誤對象。

在代碼中主要用 ErrorTypeError 這兩種最多見的類型來建立本身的錯誤對象。

可是在大多數狀況下,不少錯誤直接來自 JavaScript 引擎,例如 InternalErrorSyntaxError

下面的例子是當你嘗試從新爲 const 賦值時,將觸發 TypeError

const name = "Jules";
name = "Caty";

// TypeError: Assignment to constant variable.

當你關鍵字拼錯時,就會觸發 SyntaxError

va x = '33';
// SyntaxError: Unexpected identifier

或者,當你在錯誤的地方使用保留關鍵字時,例如在 async 函數以外的使用 await

function wrong(){
    await 99;
}

wrong();

// SyntaxError: await is only valid in async function

當在頁面中選擇不存在的 HTML 元素時,會發生 TypeError

Uncaught TypeError: button is null

除了這些「傳統的」錯誤對象外,AggregateError 對象也即將可以在 JavaScript 中使用。

AggregateError 能夠把多個錯誤很方便地包裝在一塊兒,在後面將會看到。

除了這些內置錯誤外,在瀏覽器中還能夠找到:

  • DOMException
  • DOMError 已棄用,目前再也不使用。

DOMException 是與 Web API 相關的一系列錯誤。有關完整列表,請參見 MDN

什麼是異常?

不少人認爲錯誤和異常是一回事。實際上錯誤對象僅在拋出時才成爲異常

要在 JavaScript 中引起異常,咱們使用 throw 關鍵字,後面跟錯誤對象:

const wrongType = TypeError("Wrong type given, expected number");

throw wrongType;

更常見的是縮寫形式,在大多數代碼庫中,你均可以找到:

throw TypeError("Wrong type given, expected number");

或者:

throw new TypeError("Wrong type given, expected number");

通常不會把異常拋出到函數或條件塊以外,固然也有例外狀況,例如:

function toUppercase(string) {
  if (typeof string !== "string") {
    throw TypeError("Wrong type given, expected a string");
  }

  return string.toUpperCase();
}

在代碼中咱們檢查函數的參數是否爲字符串,若是不是則拋出異常。

從技術上講,你能夠在 JavaScript 中拋出任何東西,而不只僅是錯誤對象:

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

可是,最好不要這樣作,應該老是拋出正確的錯誤對象,而不是原始類型

這樣就能夠經過代碼庫保持錯誤處理的一致性。其餘團隊成員老是可以在錯誤對象上訪問 error.messageerror.stack

當拋出異常時會發生什麼?

異常就像電梯在上升:一旦拋出一個異常,它就會在程序棧中冒泡,除非被卡在某個地方

看下面的代碼:

function toUppercase(string) {
  if (typeof string !== "string") {
    throw TypeError("Wrong type given, expected a string");
  }

  return string.toUpperCase();
}

toUppercase(4);

若是你在瀏覽器或 Node.js 中運行這段代碼,程序將中止並報告錯誤:

Uncaught TypeError: Wrong type given, expected a string
    toUppercase http://localhost:5000/index.js:3
    <anonymous> http://localhost:5000/index.js:9

另外還能夠看到發生錯誤的代碼行數。

這段報告是 棧跟蹤(stack trace),對於跟蹤代碼中的問題頗有幫助。

棧跟蹤從底部到頂部。因此是這樣的:

toUppercase http://localhost:5000/index.js:3
    <anonymous> http://localhost:5000/index.js:9

咱們能夠說:

  • 程序的第 9 行中名爲 toUppercase 的內容
  • toUppercase 在第 3 行引起了一個問題

除了在瀏覽器的控制檯中看到棧跟蹤以外,還能夠在錯誤對象的 stack 屬性上對其進行訪問。

若是異常是未捕獲的,也就是說程序員沒有采起任何措施來捕獲它,則程序將會崩潰。

你在何時及在什麼地方捕獲代碼中的異常取決於特定的用例

例如,你可能想要在棧中傳播異常,使程序徹底崩潰。當發生致命的錯誤,須要更安全地中止程序而不是處理無效數據時,你可能須要這樣作。

介紹了基礎知識以後,如今讓咱們將注意力轉向同步和異步 JavaScript 代碼中的錯誤和異常處理

同步錯誤處理

同步代碼一般很簡單,它的錯誤處理也是如此。

常規函數的錯誤處理

同步代碼按照代碼順序循序漸進的執行。讓咱們再來看前面的例子:

function toUppercase(string) {
  if (typeof string !== "string") {
    throw TypeError("Wrong type given, expected a string");
  }

  return string.toUpperCase();
}

toUppercase(4);

在這裏,引擎調用並執行 toUppercase。全部操做都同步進行。要捕獲由這種同步函數產生的異常,能夠用 try/catch/finally

try {
  toUppercase(4);
} catch (error) {
  console.error(error.message);
  // or log remotely
} finally {
  // clean up
}

一般try 處理主處理流程或者可能引起異常的函數調用。

catch 則捕獲實際的異常。它接收錯誤對象,能夠在這裏對其進行檢查(並遠程發送到生產環境中的日誌服務器)。

另外不管函數的執行結果如何,無論是成功仍是失敗,finally 中的全部代碼都會被執行。

請記住: try/catch/finally 是一個同步結構:它能夠捕獲來自異步代碼的異常

生成器函數的錯誤處理

JavaScript 中的生成器函數是一種特殊的函數。

除了在其內部做用域和使用者之間提供雙向通訊通道以外,它還能夠隨意暫停和恢復

要建立一個生成器函數,須要在關鍵字 function 以後加一個星號 *

function* generate() {
    //
}

進入函數後,可使用 yield 返回值:

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

生成器函數的返回值是迭代器對象。有兩種方法從生成器中提取值

  • 在迭代器對象上調用 next()
  • iteration with for...of.
  • 帶有 for ... of 的迭代。

以上面的代碼爲例,要從生成器獲取值,能夠這樣作:

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

const go = generate();

當調用生成器函數時,go 成了咱們的迭代器對象。

如今能夠調用go.nex() 來執行:

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(和 finally,若是須要的話)將代碼包裝在生成器中來捕獲這樣的錯誤:

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);
}

/* Output:
33
99
Tired of iterating!
*/

代碼中迭代 try 塊內的主處理流程。若是發生任何異常,就用 catch 中止。

異步錯誤處理

JavaScript 在本質上是同步的,是一種單線程語言。

諸如瀏覽器引擎之類的環境用許多 Web API 加強了 JavaScript,用來與外部系統進行交互並處理與 I/O 綁定的操做。

瀏覽器中的異步示例包括timeouts、events、Promise

異步代碼中的錯誤處理與同步代碼不一樣。

看一些例子:

計時器錯誤處理

在你開始學習 JavaScript 時,當學 try/catch/finally 以後,你可能會想把它們放在任何代碼塊中。

思考下面的代碼段:

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

這個函數將在大約 1 秒鐘後被觸發。那麼處理這個異常的正確方式是什麼?

下面的例子是無效的

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

try {
  failAfterOneSecond();
} catch (error) {
  console.error(error.message);
}

正如前面所說的,try/catch 是同步的。另外一方面,咱們有 setTimeout,這是一個用於定時器的瀏覽器 API。

到傳遞給 setTimeout 的回調運行時,try/catch 已經「消失了」。程序將會崩潰,由於咱們沒法捕獲異常。

它們在兩條不一樣的軌道上行駛

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

若是咱們不想使程序崩潰,爲了正確處理錯誤,咱們必須把 try/catch 移動到 setTimeout 的回調中。

可是這在大多數狀況下並無什麼意義。Promises 的異步錯誤處理提供了更好的方式

事件的錯誤處理

文檔對象模型中的HTML節點鏈接到 EventTargetEventTarget 是瀏覽器中全部 event emitter 的共同祖先。

這意味着咱們能夠偵聽頁面中任何 HTML 元素上的事件。Node.js 將在將來版本中支持 EventTarget

DOM 事件的錯誤處理機制遵循與異步 Web API 的相同方案。

看下面的例子:

const button = document.querySelector("button");

button.addEventListener("click", function() {
  throw Error("Can't touch this button!");
});

在這裏,單擊按鈕後會當即引起異常,應該怎樣捕獲它?下面的方法不起做用,並且不會阻止程序崩潰:

const button = document.querySelector("button");

try {
  button.addEventListener("click", function() {
    throw Error("Can't touch this button!");
  });
} catch (error) {
  console.error(error.message);
}

與前面的帶有 setTimeout 的例子同樣,傳遞給 addEventListener 的任何回調均異步執行:

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

若是不想使程序崩潰,爲了正確處理錯誤,必須把 try/catch 放在 addEventListener 的回調內。但這樣作沒有任何價值。與 setTimeout 同樣,異步代碼路徑引起的異常從外部是沒法捕獲的,這將會使程序崩潰。

How about onerror?

怎麼處理 onerror?

HTML 元素具備許多事件處理函數,例如 onclickonmouseenteronchange 等。

還有 onerror,可是它與 throw 沒有什麼關係。

每當像 <img> 標籤或 <script> 之類的 HTML 元素遇到不存在的資源時,onerror 事件處理函數都會觸發。

看下面的例子:

// omitted
<body>
    <img src="nowhere-to-be-found.png" alt="So empty!">
</body>
// omitted

當訪問缺乏或不存在資源的 HTML 文檔時,瀏覽器的控制檯會輸出如下錯誤:

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);
};

或者用更好的方法:

const image = document.querySelector("img");

image.addEventListener("error", function(event) {
  console.log(event);
});

此模式可用於加載替代資源來替換丟失的圖像或腳本

可是要記住:onerrorthrowtry/catch 無關。

用 Promise 處理錯誤

爲了說明 Promise 的錯誤處理,咱們將 「Promise」 前面的一個例子。調整如下功能:

function toUppercase(string) {
  if (typeof string !== "string") {
    throw TypeError("Wrong type given, expected a string");
  }

  return string.toUpperCase();
}

toUppercase(4);

爲了代替返回簡單的字符串或異常,能夠分別用 Promise.rejectPromise.resolve 處理錯誤和成功:

function toUppercase(string) {
  if (typeof string !== "string") {
    return Promise.reject(TypeError("Wrong type given, expected a string"));
  }

  const result = string.toUpperCase();

  return Promise.resolve(result);
}

從技術上講,這段代碼中沒有異步的東西,可是它能很好地說明這一點。

如今函數已 「promise 化」,咱們能夠經過 then 使用結果,並附加 catch處理被拒絕的Promise

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

這段代碼將會輸出:

Wrong type given, expected a string

在 Promise 領域,catch 是用於處理錯誤的結構。

除了 catchthen 以外,還有 finally,相似於 try/catch 中的 finally

相對於同步而言,Promise 的 finally 運行與 Promise 結果無關

toUppercase(99)
  .then(result => result)
  .catch(error => console.error(error.message))
  .finally(() => console.log("Run baby, run"));

切記,傳遞給 then/catch/finally 的任何回調都是由微任務隊列異步處理的。微任務優先於宏任務,例如事件和計時器。

Promise, error 和 throw

做爲拒絕 Promise 的最佳方法,提供錯誤對象很方便:

Promise.reject(TypeError("Wrong type given, expected a string"));

這樣,你能夠經過代碼庫保持錯誤處理的一致性。其餘團隊成員老是能夠指望訪問 error.message,更重要的是你能夠檢查棧跟蹤。

除了 Promise.reject 以外,能夠經過拋出異常來退出 Promise 鏈。

看下面的例子:

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

咱們用一個字符串解決一個 Promise,而後當即用 throw 打破這個鏈。

爲了阻止異常的傳播,照常使用 catch

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

這種模式在 fetch 中很常見,咱們在其中檢查響應對象並查找錯誤:

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

    return response.json();
  })
  .then(json => console.log(json));

在這裏能夠用 catch 攔截異常。若是失敗了,或者決定不去捕獲它,則異常能夠在棧中冒泡

從本質上講,這還不錯,可是在不一樣的環境下對未捕獲的 rejection 的反應不一樣。

例如,未來的 Node.js 將使任何未處理 Promise rejection 的程序崩潰:

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.

更好地捕獲他們!

錯誤處理 「promisified」 計時器

使用計時器或事件沒法捕獲從回調引起的異常。咱們在上一節中看到了例子:

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

// DOES NOT WORK
try {
  failAfterOneSecond();
} catch (error) {
  console.error(error.message);
}

Promise 提供的解決方案在於代碼的「promisification」。基本上,咱們用 Promise 包裝計時器:

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

經過 reject,咱們啓動了Promise rejection,它帶有一個錯誤對象。

這時能夠用 catch 處理異常:

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

注意:一般使用 value 做爲 Promise 的返回值,並用 reason 做爲 rejection 的返回對象。

Node.js 有一個名爲promisify的工具函數,能夠簡化舊式回調 API 的「混雜」。

Promise.all 中的錯誤處理

靜態方法 Promise.all 接受一個 Promise 數組,並返回全部解析 Promise 的結果數組:

const promise1 = Promise.resolve("All good!");
const promise2 = Promise.resolve("All good here too!");

Promise.all([promise1, promise2]).then((results) => console.log(results));

// [ 'All good!', 'All good here too!' ]

若是這些 Promise 中的任何一個被拒絕,Promise.all 都會拒絕,並返回第一個被拒絕的 Promise 中的錯誤。

爲了在 Promise.all 中處理這些狀況,須要使用 catch,就像在上一節中所作的那樣:

const promise1 = Promise.resolve("All good!");
const promise2 = Promise.reject(Error("No good, sorry!"));
const promise3 = Promise.reject(Error("Bad day ..."));

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("Always runs!"));

Promise.any 中的錯誤處理

咱們能夠將 Promise.any(Firefox> 79,Chrome> 85)視爲與 Promise.all 相反。

即便數組中的一個 Promise 拒絕,Promise.all 也會返回失敗,而 Promise.any 老是提供第一個已解決的Promise(若是存在於數組中),不管發生了什麼拒絕。

若是傳遞給 Promise.any 的 Promise 不是都被拒絕,則產生的錯誤是 AggregateError。考慮如下示例:

const promise1 = Promise.reject(Error("No good, sorry!"));
const promise2 = Promise.reject(Error("Bad day ..."));

Promise.any([promise1, promise2])
  .then(result => console.log(result))
  .catch(error => console.error(error))
  .finally(() => console.log("Always runs!"));

這裏用 catch 處理錯誤。這裏代碼的輸出是:

AggregateError: No Promise in Promise.any was resolved
Always runs!

AggregateError 對象有與基本 Error 相同的屬性,以及 Errors 屬性:

//
  .catch(error => console.error(error.errors))
//

這個屬性是拒絕產生的每一個錯誤的數組:

[Error: "No good, sorry!, Error: "Bad day ..."]

Promise.race 中的錯誤處理

靜態方法 Promise.race 接受一個 Promise 數組:

const promise1 = Promise.resolve("The first!");
const promise2 = Promise.resolve("The second!");

Promise.race([promise1, promise2]).then(result => console.log(result));

// The first!

結果是第一個贏得「race」的 Promise

那 rejection 呢?若是拒絕的 Promise 不是第一個出如今輸入數組中的對象,則 Promise.race 解析:

const promise1 = Promise.resolve("The first!");
const rejection = Promise.reject(Error("Ouch!"));
const promise2 = Promise.resolve("The second!");

Promise.race([promise1, rejection, promise2]).then(result =>
  console.log(result)
);

// The first!

若是 rejection 出如今數組的第一個元素中,則 Promise.race 被拒絕,咱們必須捕獲它:

const promise1 = Promise.resolve("The first!");
const rejection = Promise.reject(Error("Ouch!"));
const promise2 = Promise.resolve("The second!");

Promise.race([rejection, promise1, promise2])
  .then(result => console.log(result))
  .catch(error => console.error(error.message));

// Ouch!

Promise.allSettled 中的錯誤處理

Promise.allSettled 是對該語言的 ECMAScript 2020 補充。

這個靜態方法沒有什麼要處理的,由於即便一個或多個輸入 Promise 被拒絕,結果也始終是一個已解決的Promise

看下面的例子:

const promise1 = Promise.resolve("Good!");
const promise2 = Promise.reject(Error("No good, sorry!"));

Promise.allSettled([promise1, promise2])
  .then(results => console.log(results))
  .catch(error => console.error(error))
  .finally(() => console.log("Always runs!"));

咱們將由兩個 Promise 組成的數組傳遞給 Promise.allSettled:一個已解決,另外一個被拒絕。

在這種狀況下,catch 將永遠不會被執行。finally 會運行。

日誌輸出的 then 的代碼的結果是:

[
  { status: 'fulfilled', value: 'Good!' },
  {
    status: 'rejected',
    reason: Error: No good, sorry!
  }
]

async/await 的錯誤處理

JavaScript 中的 await 表示異步函數,但從維護者的角度來看,它們受益於同步函數的全部「可讀性」。

爲了簡單起見,咱們將使用先前的同步函數 toUppercase,並將 async 放在 function 關鍵字以前,將其轉換爲異步函數:

async function toUppercase(string) {
  if (typeof string !== "string") {
    throw TypeError("Wrong type given, expected a string");
  }

  return string.toUpperCase();
}

只需在函數前面加上 async,就可使函數返回一個Promise。這意味着咱們能夠在函數調用以後連接 thencatchfinally

async function toUppercase(string) {
  if (typeof string !== "string") {
    throw TypeError("Wrong type given, expected a string");
  }

  return string.toUpperCase();
}

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

當咱們從異步函數中拋出異常時,異常會成爲致使底層 Promise 被拒絕的緣由。

任何錯誤均可以經過外部的 catch 來攔截。

最重要的是,除了這種樣式外,還可使用 try/catch/finally,就像使用同步函數同樣。

在下面的例子中,咱們從另外一個函數 consumer 調用 toUppercase,該函數用 try/catch/finally 方便地包裝函數調用:

async function toUppercase(string) {
  if (typeof string !== "string") {
    throw TypeError("Wrong type given, expected a string");
  }

  return string.toUpperCase();
}

async function consumer() {
  try {
    await toUppercase(98);
  } catch (error) {
    console.error(error.message);
  } finally {
    console.log("Always runs!");
  }
}

consumer(); // Returning Promise ignored

輸出爲:

Wrong type given, expected a string
Always runs!

異步生成器的錯誤處理

JavaScript 中的異步生成器(Async generators) 不是生產簡單值,而是可以生成 Promise 的生成器函數 。

它們將生成器函數與 async 結合在一塊兒。其結果是生成器函數將 Promise 暴露給使用者的迭代器對象。

咱們用前綴爲 async 和星號 * 聲明一個異步生成器函數。

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

基於 Promise 用於錯誤處理的相同規則,異步生成器中的 throw 致使 Promise 拒絕,用 catch 進行攔截。

有兩種方法能夠把 Promise 拉出異步生成器

  • then
  • 異步迭代

從上面的例子中,在前兩個 yield 以後會有一個例外。這意味着咱們能夠作到:

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));

這段代碼的輸出是:

{ value: 33, done: false }
{ value: 99, done: false }
Something went wrong!

另外一種方法是使用異步迭代for await...of。要使用異步迭代,須要用 async 函數包裝使用者。

這是完整的例子:

async function* asyncGenerator() {
  yield 33;
  yield 99;
  throw Error("Something went wrong!"); // 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("Something went wrong!"); // Promise.reject
}

async function consumer() {
  try {
    for await (const value of asyncGenerator()) {
      console.log(value);
    }
  } catch (error) {
    console.error(error.message);
  }
}

consumer();

這段代碼的輸出是:

33
99
Something went wrong!

從異步生成器函數返回的迭代器對象也有一個 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("Let's reject!"));

go.next().then(value => console.log(value)); // value is undefined

能夠經過執行如下操做從外部處理這種狀況:

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("Let's reject!"));

go.next().then(value => console.log(value)); // value is undefined

Node.js中的錯誤處理

Node.js 中的同步錯誤處理

Node.js 中的同步錯誤處理與到目前爲止所看到的並無太大差別。

對於同步代碼try/catch/finally 能夠正常工做。

可是若是進入異步世界,事情就會變得有趣。

Node.js 中的異步錯誤處理:回調模式

對於異步代碼,Node.js 強烈依賴於兩個習慣用法:

  • 回調模式。
  • 事件發射器(event emitter)。

在回調模式中,異步 Node.js API 接受經過事件循環處理的函數,並在調用棧爲空時當即執行。

看下面的代碼:

const { readFile } = require("fs");

function readDataset(path) {
  readFile(path, { encoding: "utf8" }, function(error, data) {
    if (error) console.error(error);
    // 處理數據
  });
}

若是從這個清單中提取回調,則能夠看到應該如何處理錯誤:

//
function(error, data) {
    if (error) console.error(error);
    // 處理數據
  }
//

若是經過使用 fs.readFile 讀取給定路徑而引發任何錯誤,將獲得一個錯誤對象。

在這一點上,咱們能夠:

  • 簡單地把錯誤對象輸出到日誌。
  • 引起異常。
  • 將錯誤傳遞給另外一個回調。

要拋出異常,能夠執行如下操做:

const { readFile } = require("fs");

function readDataset(path) {
  readFile(path, { encoding: "utf8" }, function(error, data) {
    if (error) throw Error(error.message);
    // do stuff with the data
  });
}

可是,與 DOM 中的事件和計時器同樣,這個異常將會使程序崩潰。下面的代碼嘗試經過 try/catch 的處理將不起做用:

const { readFile } = require("fs");

function readDataset(path) {
  readFile(path, { encoding: "utf8" }, function(error, data) {
    if (error) throw Error(error.message);
    // do stuff with the 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);
    // do stuff with the data
  });
}

顧名思義,errorHandler 是一個簡單的錯誤處理函數:

function errorHandler(error) {
  console.error(error.message);
  // do something with the error:
  // - write to a log.
  // - send to an external logger.
}

Node.js 中的異步錯誤處理:事件發射器

咱們在 Node.js 中所作的大部分工做都是基於事件的。在大多數狀況下,須要與發射器對象和一些觀察者偵聽消息進行交互。

Node.js 中的任何事件驅動模塊(例如net)都會擴展名爲 EventEmitter 的根類 。

Node.js中的 EventEmitter 有兩種基本方法:onemit

看下面這個簡單的 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!");
});

在這裏,咱們監聽兩個事件:listeningconnection

除了這些事件以外,事件發射器還暴露了 error 事件,以防發生錯誤。

在 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; // Unhandled 'error' event
      ^

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

而且程序不會崩潰。

總結

在本文中,咱們介紹了從簡單的同步代碼到高級異步原語,以及整個 JavaScript 的錯誤處理

在 JavaScript 程序中,能夠經過多種方式來顯示異常。

同步代碼中的異常是最容易捕獲的。而來自異步代碼路徑的異常處理可能會有些棘手。

同時,瀏覽器中的新 JavaScript API 幾乎都朝着 Promise 的方向發展。這種廣泛的模式使得用 then/catch/finally 或用 try/catch 來處理 async/await 異常更加容易。

看完本文後,你應該可以識別程序中可能會出現的全部不一樣狀況,並正確捕獲異常。

173382ede7319973.gif


本文首發微信公衆號:前端先鋒

歡迎掃描二維碼關注公衆號,天天都給你推送新鮮的前端技術文章

歡迎掃描二維碼關注公衆號,天天都給你推送新鮮的前端技術文章

歡迎繼續閱讀本專欄其它高贊文章:


相關文章
相關標籤/搜索