家好,我是 Echa。
本文將帶你了解 JavaScript 中常見的錯誤類型,處理同步和異步 JavaScript/Node.js 代碼中錯誤和異常的方式,以及錯誤處理最佳實踐!
JavaScript 中的錯誤是一個對象,在發生錯誤時會拋出該對象以停止程序。在 JavaScript 中,可以通過構造函數來創建一個新的通用錯誤:
const err = new Error("Error");
當然,也可以省略 new 關鍵字:
const err = Error("Error");
Error 對象有三個屬性:
例如,創建一個 TypeError 對象,該消息將攜帶實際的錯誤字符串,其 name 將是“TypeError”:
const wrongType = TypeError("Expected number");
wrongType.message; // 'Expected number'
wrongType.name; // 'TypeError'
堆棧跟蹤是發生異?;蚓娴仁录r程序所處的方法調用列表:
它首先會打印錯誤名稱和消息,然后是被調用的方法列表。每個方法調用都說明其源代碼的位置和調用它的行??梢允褂么藬祿頌g覽代碼庫并確定導致錯誤的代碼段。此方法列表以堆疊的方式排列。它顯示了異常首先被拋出的位置以及它如何通過堆棧方法調用傳播。為異常實施捕獲不會讓它通過堆棧向上傳播并使程序崩潰。
對于 Error 對象,Firefox 還實現了一些非標準屬性:
JavaScript 中有一系列預定義的錯誤類型。只要使用者沒有明確處理應用程序中的錯誤,它們就會由 JavaScript 運行時自動選擇和定義。
JavaScript中的錯誤類型包括:
這些錯誤類型都是實際的構造函數,旨在返回一個新的錯誤對象。最常見的就是 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 中最常見的錯誤類型,并了解它們發生的時間和原因。
SyntaxError 表示語法錯誤。這些錯誤是最容易修復的錯誤之一,因為它們表明代碼語法中存在錯誤。由于 JavaScript 是一種解釋而非編譯的腳本語言,因此當應用程序執行包含錯誤的腳本時會拋出這些錯誤。在編譯語言的情況下,此類錯誤在編譯期間被識別。因此,在修復這些問題之前,不會創建應用程序二進制文件。
SyntaxError 發生的一些常見原因是:
TypeError 是 JavaScript 應用程序中最常見的錯誤之一,當某些值不是特定的預期類型時,就會產生此錯誤。
TypeError 發生的一些常見原因是:
ReferenceError 表示引用錯誤。當代碼中的變量引用有問題時,會發生 ReferenceError。可能忘記在使用變量之前為其定義一個值,或者可能試圖在代碼中使用一個不可訪問的變量。在任何情況下,通過堆棧跟蹤都可以提供充足的信息來查找和修復有問題的變量引用。
ReferenceErrors 發生的一些常見原因如下:
RangeError 表示范圍錯誤。當變量設置的值超出其合法值范圍時,將拋出 RangeError。它通常發生在將值作為參數傳遞給函數時,并且給定值不在函數參數的范圍內。當使用記錄不完整的第三方庫時,有時修復起來會很棘手,因為需要知道參數的可能值范圍才能傳遞正確的值。
RangeError 發生的一些常見場景如下:
URIError 表示 URI錯誤。當 URI 的編碼和解碼出現問題時,會拋出 URIError。JavaScript 中的 URI 操作函數包括:decodeURI、decodeURIComponent 等。如果使用了錯誤的參數(無效字符),就會拋出 URIError。
EvalError 表示 Eval 錯誤。當 eval() 函數調用發生錯誤時,會拋出 EvalError。不過,當前的 JavaScript 引擎或 ECMAScript 規范不再拋出此錯誤。但是,為了向后兼容,它仍然是存在的。
如果使用的是舊版本的 JavaScript,可能會遇到此錯誤。在任何情況下,最好調查在eval()函數調用中執行的代碼是否有任何異常。
InternalError 表示內部錯誤。在 JavaScript 運行時引擎發生異常時使用。它表示代碼可能存在問題也可能不存在問題。
InternalError 通常只發生在兩種情況下:
解決此錯誤最合適的方法就是通過錯誤消息確定原因,并在可能的情況下重構應用邏輯,以消除 JavaScript 引擎上工作負載的突然激增。
注意: 現代 JavaScript 中不會拋出 EvalError 和 InternalError。
雖然 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 {
}
}
很多人認為錯誤和異常是一回事。實際上,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 對象,而不是原語。
異常一旦拋出,就會在程序堆棧中冒泡,除非在某個地方被捕獲。
來看下面的例子:
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 代碼中的錯誤和異常處理。
同步代碼會按照代碼編寫順序執行。讓我們再看看前面的例子:
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 中的代碼都會運行。
JavaScript 中的生成器函數是一種特殊類型的函數。它可以隨意暫停和恢復,除了在其內部范圍和消費者之間提供雙向通信通道。為了創建一個生成器函數,需要在 function 關鍵字后面加上一個 *:
function* generate() {
//
}
只要進入函數,就可以使用 yield 來返回值:
function* generate() {
yield 33;
yield 99;
}
生成器函數的返回值是一個迭代器對象。要從生成器中提取值,可以使用兩種方法:
以上面的代碼為例,要從生成器中獲取值,可以這樣做:
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 捕獲它。
瀏覽器中的異步包括定時器、事件、Promise 等。異步世界中的錯誤處理與同步世界中的處理不同。下面來看一些例子。
上面我們介紹了如何使用 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
我們可以監聽頁面中任何 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 是如何簡化異步代碼的錯誤處理的。
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);
});
此模式對于加載備用資源以代替丟失的圖像或腳本很有用。不過需要記?。簅nerror 與 throw 或 try/catch 是無關的。
下面來通過最上面的 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 的回調都是由微任務隊列異步處理的。 它們是微任務,優先于事件和計時器等宏任務。
作為拒絕 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.
所以,最好去捕獲錯誤。
對于計時器或事件,不能捕獲回調拋出的異常。上面有一個例子:
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 作為拒絕的返回對象。
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"));
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"));
此屬性是一個包含所有被拒絕的錯誤信息的數組:
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
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 的數組:一個已解決,另一個已拒絕。
輸出結果如下:
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();
輸出結果如下:
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));
Node.js 中的同步錯誤處理與 JavaScript 是一樣的,可以使用 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);
// 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
}
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
最后,我們來看看處理 JavaScript 異常的最佳實踐!
錯處理的第一個最佳實踐就是不要過度使用“錯誤處理”。通常,我們會在外層處理錯誤,從內層拋出錯誤,這樣一旦出現錯誤,就可以更好地理解是什么原因導致的。
然而,開發人員常犯的錯誤之一是過度使用錯誤處理。有時這樣做是為了讓代碼在不同的文件和方法中看起來保持一致。但是,不幸的是,這些會對應用程序和錯誤檢測造成不利影響。
因此,只關注代碼中可能導致錯誤的地方,錯誤處理將有助于提高代碼健壯性并增加檢測到錯誤的機會。
盡管許多瀏覽器都遵循一個通用標準,但某些特定于瀏覽器的 JavaScript 實現在其他瀏覽器上卻失敗了。例如,以下語法僅適用于 Firefox:
catch(e) {
console.error(e.filename + ': ' + e.lineNumber);
}
因此,在處理錯誤時,盡可能使用跨瀏覽器友好的 JavaScript 代碼。
當發生錯誤時,我們應該得到通知以了解出了什么問題。這就是錯誤日志的用武之地。JavaScript 代碼是在用戶的瀏覽器中執行的。因此,需要一種機制來跟蹤客戶端瀏覽器中的這些錯誤,并將它們發送到服務器進行分析。
可以嘗試使用以下工具來監控并上報錯誤:
Node.js 環境支持使用中間件向服務端應用中添加功能。因此可以創建一個錯誤處理中間件。使用中間件的最大好處是所有錯誤都在一個地方集中處理??梢赃x擇啟用/禁用此設置以輕松進行測試。
以下是創建基本中間件的方法:
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)
現在可以在中間件內定義自定義邏輯以適當地處理錯誤。而無需再擔心在整個代碼庫中實現單獨的錯誤處理結構。
我們可能永遠無法涵蓋應用中可能發生的所有錯誤。因此,必須實施回退策略以捕獲應用中所有未捕獲的異常。
可以這樣做:
process.on('uncaughtException', error => {
console.log("ERROR: " + String(error))
// 其他處理機制
})
還可以確定發生的錯誤是標準錯誤還是自定義操作錯誤。根據結果,可以退出進程并重新啟動它以避免意外行為。
與異常不同的是,promise 拒絕不會拋出錯誤。因此,一個被拒絕的 promise 可能只是一個警告,這讓應用有可能遇到意外行為。因此,實現處理 promise 拒絕的回退機制至關重要。
可以這樣做:
const promiseRejectionCallback = error => {
console.log("PROMISE REJECTED: " + String(error))
}
process.on('unhandledRejection', callback)
JavaScript(JS)是一門高級的、解釋型的動態編程語言。用于 HTML或web應用,可被瀏覽器直接執行。
完整的JavaScript包括以下幾個部分:
JavaScript的基本特點:
JavaScript常用來完成以下任務:
1.嵌入HTML
HTML 中的腳本必須位于 <script> 與 </script> 標簽之間。
腳本可被放置在 HTML 頁面的 <body> 和 <head> 部分中。
<!DOCTYPE html>
<html>
<head>
<script>
document.write("hello world");
</script>
</head>
<body>
.
<script>
document.write("hello world");
</script>
.
</body>
</html>
如果 <script> 放在 body 中,建議放在底部。因為瀏覽器會按照代碼在文件中的順序解析 HTML。如果先加載的 JavaScript 期望修改其下方的 HTML,那么它可能由于 HTML 尚未被加載而失效。所以,要將 JavaScript 代碼放在 body 中,應置于 HTML 頁面底部。
2.外部js腳本
也可以把腳本保存到外部文件中。外部文件通常包含被多個網頁使用的代碼。
外部 JavaScript 文件的文件擴展名是 .js。外部文件不使用 <script> 標簽,直接寫 javascript 代碼。
如需使用外部文件,請在 <script> 標簽的 "src" 屬性中設置該 .js 文件:
<!DOCTYPE html>
<html>
<body>
<script src="xx.js"></script>
</body>
</html>
3.在console調試
可以進入瀏覽器中console,如chrome F12 進入console,以命令行的形式輸入js命令
4.事件觸發
把 JavaScript 代碼放入函數中,在事件發生時調用該函數。
<button type="button" onclick="myFunction()">按鈕</button>
<script>
function myFunction()
{
alert("hello");
}
</script>
JavaScript 使用關鍵字 var 來定義變量, 使用等號來為變量賦值
也可以使用let,const來定義,這三者的區別是:var與let涉及塊級作用域,let 命令只在所在的代碼塊 {} 內有效,具體參考這里;const定義一個只讀常量
一般用var即可,let更推薦
var a; //聲明
var a = 10; //初始化
a = 20 ; //賦值
注意: 在JavaScript中,所有代碼指令都會以分號結尾 (;)
JavaScript中數據類型主要有:
變量 | 解釋 | 示例 |
String | 字符串(一串文本)。字符串的值必須將用引號(單雙均可,必須成對)擴起來。 | var name = "John"; 字符訪問:name[0] name[1] |
Number | 數字。JavaScript 只有一種數字類型。數字可以帶小數點,也可以不帶 | let x1=34.00; |
Boolean | 布爾值(真 / 假)。 true/false 是 JS 里的特殊關鍵字,無需引號。 | let x=true; |
Array | 數組,用于在單一引用中存儲多個值的結構。 | let cars=["Saab","Volvo","BMW"]; 元素引用方法:cars[0],cars[1] |
Object | 對象,JavaScript 里一切皆對象,一切皆可儲存在變量里。 對象由花括號分隔。在括號內部,對象的屬性以名稱和值對的形式 (name : value) 來定義。屬性由逗號分隔 | let person={firstname:"John", lastname:"Doe", id:5566}; let newObject = document.querySelector('h1'); 對象屬性訪問:object.AttributesName object[''AttributesName''] |
算數運算符主要有:
+ 加法 | - 減法 | * 乘法 | / 除法 |
% 取模 | ++自增 | --自減 |
比較運算符主要有:
==等于 | === 絕對等于(值和類型均相等) | != 不等于 | !== 不絕對等于(值和類型有一個不相等,或兩個都不相等) |
> 大于 | < 小于 | >= 大于或等于 | <= 小于或等于 |
在常規的比較中,數據類型是被忽略的
var x = 10;
var y = "10";
if (x == y) //返回true,所以要用===
1.條件語句
if 語句只有當指定條件為 true 時,該語句才會執行代碼。
if (condition)
{
當條件為 true 時執行的代碼
}
使用 if....else 語句在條件為 true 時執行代碼,在條件為 false 時執行其他代碼。
if (condition)
{
當條件為 true 時執行的代碼
}
else
{
當條件不為 true 時執行的代碼
}
使用 if....else if...else 語句來選擇多個代碼塊之一來執行。
if (condition1)
{
當條件 1 為 true 時執行的代碼
}
else if (condition2)
{
當條件 2 為 true 時執行的代碼
}
else
{
當條件 1 和 條件 2 都不為 true 時執行的代碼
}
三目運算
let variablename = (condition)?value1:value2
condition成立則variablename = value1 否則value2
2.循環語句
JavaScript 支持不同類型的循環:
for 循環
for (語句 1; 語句 2; 語句 3)
{
被執行的代碼塊
}
//語句 1 (代碼塊)開始前執行
//語句 2 定義運行循環(代碼塊)的條件
//語句 3 在循環(代碼塊)已被執行之后執行
for/in遍歷
for (x in 可遍歷對象) // x 為屬性名
{
執行代碼塊
}
while 循環會在指定條件為真時循環執行代碼塊。
while (條件)
{
需要執行的代碼
}
do/while 循環是 while 循環的變體。該循環會在檢查條件是否為真之前執行一次代碼塊,然后如果條件為真的話,就會重復這個循環。
do
{
需要執行的代碼
}
while (條件);
3.函數語法
函數就是包裹在花括號中的代碼塊,前面使用了關鍵詞 function:
function functionname()
{
// 執行代碼
}
加上形參
function myFunction(var1,var2)
{
代碼
return var3 //使用 return 語句時,函數會停止執行,并返回指定的值
}
匿名函數,函數存儲在變量中,不需要函數名稱,通常通過變量名來調用。
var x = function (a, b) {return a * b};
var z = x(4, 3);
4.異常判斷
try 語句允許我們定義在執行時進行錯誤測試的代碼塊。
catch 語句允許我們定義當 try 代碼塊發生錯誤時,所執行的代碼塊。
JavaScript 語句 try 和 catch 是成對出現的。
finally 語句不論之前的 try 和 catch 中是否產生異常都會執行該代碼塊。
try {
... //異常的拋出
} catch(e) {
... //異常的捕獲與處理
} finally {
... //結束處理
}
在瀏覽器中,頁面被加載的時候,會自動生成這個頁面的DOM對象(document)
DOM對象是一個樹模型
可以通過這個對象:
1.查找HTML元素
通過id查找
var x=document.getElementById("intro");
//如果找到該元素,則該方法將以對象(在 x 中)的形式返回該元素。
//如果未找到該元素,則 x 將包含 null。
通過標簽查找
var y=document.getElementsByTagName("div");
//如果有多個同類標簽,則返回多個對象的數組
通過類名查找
var x=document.getElementsByClassName("intro");
因為查找返回的元素也是對象,所以可以對它再進行查找
var x=document.getElementById("main");
var y=x.getElementsByTagName("p");
//本例查找 id="main" 的元素,然后查找 id="main" 元素中的所有 <p> 元素
2.修改HTML元素
改變HTML輸出流
document.write();
//如果在文檔(DOM)加載完成之后使用則會覆蓋該文檔
修改 HTML 內容
document.getElementById(id).innerHTML=新的 HTML內容
改變 HTML 元素的屬性
document.getElementById(id).attribute=新屬性值
3.DOM事件
事件可以是瀏覽器行為,也可以是用戶行為,當事件觸發時,可以執行對應的js代碼
常見事件
事件 | 描述 |
onchange | HTML 元素改變 |
onclick | 用戶點擊 HTML 元素 |
onmouseover | 用戶在一個HTML元素上移動鼠標 |
onmouseout | 用戶從一個HTML元素上移開鼠標 |
onkeydown | 用戶按下鍵盤按鍵 |
onload | 瀏覽器已完成頁面的加載 |
HTML 元素中可以添加事件屬性,并添加 JavaScript 代碼,當此元素的事件被觸發時,就會觸發執行js代碼
<button onclick="getElementById('demo').innerHTML=Date()">現在的時間是?</button>
<p id="demo"></p>
也可以由DOM對象分配事件,因為事件也是html的一個屬性
<script>
document.getElementById("myBtn").onclick=function(){displayDate()};
</script>
addEventListener() 方法
element.addEventListener("click", function(){ alert("Hello World!"); });
語法為
element.addEventListener(event, function, useCapture);
第一個參數是事件的類型 (如 "click" 或 "mousedown").
第二個參數是事件觸發后調用的函數。
第三個參數是個布爾值用于描述事件是冒泡還是捕獲。該參數是可選的。
詳細的事件類型參考https://developer.mozilla.org/zh-CN/docs/Web/Events,注意與在元素屬性中的事件是不同的
.什么是javascript?
JS:javascript
解釋型腳本語言
運行環境:
2.js組成:
3.js特點:
4.瀏覽器內核:
作用:負責頁面內容的渲染
內核:
注意:不同瀏覽器內核不同
5.js運行環境:
之前說過了,解釋器有nodejs和瀏覽器,我們這里說瀏覽器運行方式:
F12(檢查)-->console,直接在console里面輸入JS代碼回車運行
6.使用方式
同css使用方式一樣,分為三種:
js代碼直接嵌入在標簽事件中
onclick:當單擊時所作的操作
用"<script> </script>"標簽包起來
寫在head或者body中的任意位置
腳本內容之間同css一樣用“;”分割
方法:<script src=""></script>
創建js文件,并編寫js代碼(如:test.js),在頁面中引入js文件
1.每個頁面可以引用多個js文件(學習時1個即可),但引用多個js文件時,要注意js的引用順序,順序不同,可能導致效果不一樣,甚至后面的js永遠不會執行
2.外部引用時,script標簽中不允許出現js代碼,出現也是無效的
7.語法規范:
語句:
console.log() 正確
Console.log() 錯誤(原因:字母大寫了)
console.log(); 正確
Console.log(); 錯誤(原因:分號中文的)
8.關于注釋
*請認真填寫需求信息,我們會在24小時內與您取得聯系。