在咱們的程序中,事情並不是一路順風。javascript
特別是在某些狀況下,咱們可能但願在中止程序或在發生不良情況時通知用戶。
例如:html
在全部的這些狀況下,咱們做爲程序員都會產生錯誤,或者讓編程引擎爲咱們建立一些錯誤。前端
在建立錯誤以後,咱們能夠向用戶通知消息,或者能夠徹底中止執行。java
JavaScript 中的錯誤是一個對象,隨後被拋出,用以終止程序。node
要在 JavaScript 中建立新錯誤,咱們調用相應的構造函數。例如,要建立一個新的通用錯誤,能夠執行如下操做:程序員
const err = new Error("Something bad happened!");
建立錯誤對象時,也能夠省略關鍵字 new
:面試
const err = Error("Something bad happened!");
建立後,錯誤對象將顯示三個屬性:編程
message
:帶有錯誤信息的字符串。name
:錯誤的類型。stack
:函數執行的棧跟蹤。例如,若是咱們用適當的消息建立一個新的 TypeError
對象,則 message
將攜帶實際的錯誤字符串,而 name
則爲 TypeError
:json
const wrongType = TypeError("Wrong type given, expected number"); wrongType.message; // "Wrong type given, expected number" wrongType.name; // "TypeError"
Firefox 還實現了一堆非標準屬性,例如 columnNumber
,filename
和 lineNumber
。segmentfault
JavaScript 中有不少類型的錯誤,即:
Error
EvalError
InternalError
RangeError
ReferenceError
SyntaxError
TypeError
URIError
請記住,全部這些錯誤類型都是實際構造函數,旨在返回一個新的錯誤對象。
在代碼中主要用 Error
和 TypeError
這兩種最多見的類型來建立本身的錯誤對象。
可是在大多數狀況下,不少錯誤直接來自 JavaScript 引擎,例如 InternalError
或 SyntaxError
。
下面的例子是當你嘗試從新爲 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.message
或 error.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
咱們能夠說:
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()
。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節點鏈接到 EventTarget
,EventTarget
是瀏覽器中全部 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
同樣,異步代碼路徑引起的異常從外部是沒法捕獲的,這將會使程序崩潰。
HTML 元素具備許多事件處理函數,例如 onclick
、onmouseenter
和 onchange
等。
還有 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); });
此模式可用於加載替代資源來替換丟失的圖像或腳本。
可是要記住:onerror
與 throw
或 try/catch
無關。
爲了說明 Promise 的錯誤處理,咱們將 「Promise」 前面的一個例子。調整如下功能:
function toUppercase(string) { if (typeof string !== "string") { throw TypeError("Wrong type given, expected a string"); } return string.toUpperCase(); } toUppercase(4);
爲了代替返回簡單的字符串或異常,能夠分別用 Promise.reject
和 Promise.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
是用於處理錯誤的結構。
除了 catch
和 then
以外,還有 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 的最佳方法,提供錯誤對象很方便:
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.
更好地捕獲他們!
使用計時器或事件沒法捕獲從回調引起的異常。咱們在上一節中看到了例子:
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 數組,並返回全部解析 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
(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 數組:
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
是對該語言的 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! } ]
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。這意味着咱們能夠在函數調用以後連接 then
,catch
和 finally
:
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 中的同步錯誤處理與到目前爲止所看到的並無太大差別。
對於同步代碼,try/catch/finally
能夠正常工做。
可是若是進入異步世界,事情就會變得有趣。
對於異步代碼,Node.js 強烈依賴於兩個習慣用法:
在回調模式中,異步 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 中的任何事件驅動模塊(例如net)都會擴展名爲 EventEmitter 的根類 。
Node.js中的 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。
除了這些事件以外,事件發射器還暴露了 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
異常更加容易。
看完本文後,你應該可以識別程序中可能會出現的全部不一樣狀況,並正確捕獲異常。