件編程難學?html基礎,跟著淼哥學php全棧之路7
感謝小伙伴們的持續關注,小編這幾天太忙了,是在抱歉沒有及時更新文章,請見諒。
上次我們講解了web開發的基礎知識,也知道了web開發的原理。
這次我們繼續講解web開發語言html。上次課程我們介紹了web開發中html就相當骨骼,css就相當于樣式,js就相當于動作。
那么究竟什么才是html呢?究竟如何來學習呢?
?HTML(HyperTextMark-upLanguage)即超文本標記語言
?HTML 是用來描述網頁的一種語言。
?HTML 不是一種編程語言,而是一種標記語言
html并不是真正意義上的編程語言,而是由一套標簽和內容組成的標記語言。
標記語言是一套標記標簽 (markup tag)
?HTML 使用標記標簽來描述網頁。(換句話說,碰到瀏覽器認識的標簽,就按照這個標簽的意義來渲染網頁)
?HTML 文檔包含了HTML 標簽及文本內容。
?HTML 文檔也叫做 web 頁面
?HTML 文檔描述網頁
?HTML 文檔包含 HTML 標簽和純文本
?HTML 文檔也被稱為網頁
?Web 瀏覽器的作用是讀取 HTML 文檔,并以網頁的形式顯示出它們。瀏覽器不會顯示 HTML 標簽,而是使用標簽來解釋頁面的內容:
html是由W3C這個組織維護的。如果不清楚的小伙伴,可以查看我的上一篇文章了解內容。
我將html的學習階段分三個階段、初級、中級、高級。(當然高級也不難)o(* ̄︶ ̄*)o
1、html編寫方法
?使用手工編寫
記事本和各類IED進行編寫,文件名存為.htm .html格式。
?使用可視化HTML編輯器
Frontpage、Dreamweaver等所見即所得的編輯器進行編寫。
?由web服務器(或稱HTTP服務器)一方實時動態的生成
由WEB相關編程語言,動態生成html文件。
有的小伙伴,可能不清楚如何開啟電腦顯示后綴名。
我的電腦是windows10操作系統。這樣就可以在電腦中顯示文件的后綴名了。
其中.html就是我們的網頁了。
2、html文件命名
?*.htm或*.html
?無空格
?無特殊字符(例如&符號)只可以有”_”,只可以為英文、數字(為什么不能以中文方式命名?)
?區分大小寫
?首頁文件名默認為:index.htm或index.html 和其他動態語言文件如index.php index.asp index.jsp
為什么不建議中文命名!
1、計算機是老外開發的、操作系統是老外開發,HTML語法也是老外開發的、所以網頁文件默認也就只能按老外的命名規則了(用英語)中文需要經過一定的轉碼才能讓瀏覽器認識,不然根本不認識
2、這個就是中文命名的后果咯。找不到對應的路徑下的文檔。
The requested URL /demo/qzb/????o????é?¢.html was not found on this server. Additionally, a 404 Not Found error was encountered while trying to use an ErrorDocument to handle the request.
2、 使用Apache或Nginx(web服務器)部署時需要修改配置才能支持中文、外國的軟件默認都不支持中文的, 建議不要使用中文作為文件名。
3、html的基本架構
?一個HTML文檔是由一系列的元素和標記組成。
<head></head>元素和標簽都是一個意思。標簽都是成對出現的。當然也有單閉合標簽后面講。
?元素名不區分大小寫,HTML用標記來規定元素的屬性和它在文件中的位置,HTML超文本文檔分文檔頭和文檔體兩部分
在文檔頭里,對這個文檔進行了一些必要的定義,文檔體中才是要顯示的各種文檔信息。
4、HTML代碼格式
<!DOCTYPE html>-------------------------DOCTYPE 聲明了文檔類型
<html> ------------------------------------文檔類型html開始標記
<head> -----------------------------------頭部標記
<title>我的第一個標題</title>
</head> ----------------------------------頭部結束標記
<body> ---------------------------------文件主體
<h1>我的第一個標題</h1>
<p>我的第一個段落。</p>
</body> ----------------------------------文件主體結束
</html> ----------------------------------文檔類型html結束標記
<html> 與 </html> 之間的文本描述網頁。
<body> 與 </body> 之間的文本是可見的頁面內容。
<h1> 與 </h1> 之間的文本被顯示為標題。
<p> 與 </p> 之間的文本被顯示為段落。
head部分 有點像人的思想,不能被別人看到的,有很多內容是給http協議和電腦看的信息。
body部分,是瀏覽器具體渲染的內容,可以被別人看到的內容。
5、從html歷史來認識html。
現在我們常常習慣于用數字來描述HTML的版本(如:HTML5 這是一個泛稱),但是最初的時候我們并沒有HTML1,而是1993年IETF團隊的一個草案,并不是成型的標準。
兩年之后,在1995年HTML有了第二版,即HTML2.0。
有了以上的兩個歷史版本,HTML的發展可謂突飛猛進。
1996年HTML3.2成為W3C推薦標準。之后在1997年和1999年,作為升級版本的4.0和4.01也相繼成為W3C的推薦標準。
在2000年基于HTML4.01的ISO HTML 成為了國際標準化組織和國際電工委員會的標準。
于是被沿用至今,這期間雖然有點小的改動但大方向上終歸沒有什么變化。
從1993-2000之間短短的7年時間,HTML 語言有著很大的發展,基于諸多人的努力,終于產生了我們現在用的HTML語言。
由于當時html的前端程序員編寫html頁面雜亂無章,比如<h1>標簽有開始,沒有結束標簽</h1>,于是w3c組織又出了一種新的標記語言打算代替html語言。就是我們知道的XML語言(eXtensible Markup Language,可擴展標記語言)來替代html。
但是w3c組織是在是太天真了。習慣已經養成了,程序員很難改掉自己編寫的習慣,所以這個計劃沒能完成。
于是在html的基礎上,來了一個叫xhtml的標記語言,xhtml和html4沒有任何區別,只是多了幾個小規定。
?XHTML 指可擴展超文本標簽語言(EXtensible HyperText Markup Language)。
?XHTML 的目標是取代 HTML。
?XHTML 與 HTML 4.01 幾乎是相同的。
?XHTML 是更嚴格更純凈的 HTML 版本。
?XHTML 是作為一種 XML 應用被重新定義的 HTML。
?XHTML 是一個 W3C 標準。
?XHTML 是 HTML 與 XML(擴展標記語言)的結合物。
?XHTML 包含了所有與 XML 語法結合的 HTML 4.01 元素。
XHTML和html的區別?
而xml也改變了自己的命運,后來也被廣泛應用,主要作用是傳輸和存儲。(后面會講到)
w3c這個組織在2004年的時候認為Html已經發展到了巔峰,就一直沒有進行更新。
這個時候出現了一種新的技術,叫ajax改變了整個web領域的格局。 WHATWG(Web Hypertext Application Technology Working Group 互聯網應用科技工作小組)這個組織,在2004年就說w3c不發布,我自己發布Web Applications 1.0(html5前身)。
于 2007 年被 W3C 接納, 并成立了新的 HTML 工作團隊。
在 2008 年 1 月 22 日,第一份正式草案已公布,預計將在 2010 年 9 月正式 向公眾推薦。WHATWG 表示該規范是目前正在進行的工作,仍須多年的努力。
換句話,W3C這個組織看到了WHATWG發布的版本,發現原來html還可以這么玩?不行html一直是由我來維護的。來吧,把這個必須交給我維護.........就有了現在的html5.
而現在的h5是一個泛稱,并不單單指的是html的版本。而是指html5+css3+javascript的一個統稱。
6、html標簽的分類
?標簽分兩種結構:雙閉合標簽和單閉合標簽
雙閉合標簽
<標簽名 屬性名1=值1 屬性名2=值2 ..>標簽中的內容</標簽>
單閉合標簽
<標簽名 屬性1=值1 屬性2=值2 ../>
例如:
<input type=text id=“name”/>
7、<!DOCTYPE>聲明(DTD)
<!DOCTYPE>是用來聲明文檔類型的,瀏覽器只有了解文檔的類型,才能正確的顯示文檔。(聲明必須是 HTML 文檔的第一行,位于 <html> 標簽之前)
從web誕生早期至今,已經發展處多個HTML版本:
對于已經發布了這么多的版本,那么瀏覽器該如何解釋這些版本的html網頁呢?到底你的網頁是利用哪個版本的標記語言來寫的呢?是用html2.0還是html5版本來寫的呢?比如我用html3.2版本寫了一個很大的項目。如果要修改這個項目要修改好幾年。這個時候就體現出DTD的作用了。
就是使用DTD來進行區分的。
我們來看看常用DTD的格式吧。
HTML 頂級元素 可用性 "注冊//組織//類型 標簽//定義 語言""URL";
上面是一個html的格式。
例:
HTML4.01
<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Frameset//EN" "http://www.w3.org/TR/html4/frameset.dtd">
XHTML
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.1//EN" "http://www.w3.org/TR/xhtml11/DTD/xhtml11.dtd">
HTML5
<!DOCTYPE html>
看出來區別了吧。以前的DTD在編寫的時候,簡直是程序員的噩夢。反正我是記不住,有可以記得住的大神嗎?也可以站出來指教指教。o(* ̄︶ ̄*)o開玩笑了。
那么以前的DTD到底是什么意思呢?我們來繼續分析。
以XHTML的DTD為示例,我們來講解下。
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.1//EN" "http://www.w3.org/TR/xhtml11/DTD/xhtml11.dtd">
當然,如果掌握了DTD的語法,我們就知道如何來定義DTD了。具體會在講解PHP解析XML來詳細講解DTD定義。
DTD顯示會有兩種模式
標準模式的排版 和JS運作模式都是以該瀏覽器支持的最高標準運行。
在兼容模式(怪異模式)中,頁面以寬松的向后兼容的方式顯示,模擬老式瀏覽器的行為以防止站點無法工作。簡單的說,就是盡可能的顯示能顯示的東西給用戶看。
DOCTYPE不存在或格式不正確會導致文檔以兼容模式呈現
標準模式與兼容模式(怪異模式)各有什么區別?(了解即可)
1.width不同
在嚴格模式中 :width是內容寬度 ,元素真正的寬度=margin-left + border-left-width + padding-left + width + padding-right + border-right- width + margin-right;
在兼容模式中 :width則是元素的實際寬度 ,內容寬度=width - ( padding-left + padding-right + border-left-width + border-right-width)
2.兼容模式下可設置百分比的高度和行內元素的高寬
在Standards(標準)模式下,給span等行內元素設置wdith和height都不會生效,而在兼容模式下,則會生效。
在standards(標準)模式下,一個元素的高度是由其包含的內容來決定的,如果父元素沒有設置高度,子元素設置一個百分比的高度是無效的。
3.用margin:0 auto設置水平居中在IE下會失效 使用margin:0 auto在standards模式下可以使元素水平居中,但在兼容模式下卻會失效(用text-align屬性解決) body{text-align:center};#content{text-align:left}
4.兼容模式下Table中的字體屬性不能繼承上層的設置,white-space:pre會失效,設置圖片的padding會失效。
有的小伙伴可能會說。小編你寫的這些我看不懂。沒關系,等你學了后面的知識在回過頭來看,就會懂了?,F在就知道有區別就可以了。
今天就寫這么多吧。下篇文章繼續帶領小伙伴來認識html。
小編手寫不宜,請小伙伴們多多留言點贊哦。。小編十分感謝。
(注:本內容屬于原創,未經許可嚴禁抄襲。內容真實性已考證,圖片來源于網絡,圖片如有侵權請聯系作者刪除)
家好,我是 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);
});
此模式對于加載備用資源以代替丟失的圖像或腳本很有用。不過需要記住:onerror 與 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)
者:瘋狂的技術宅
轉發鏈接:https://mp.weixin.qq.com/s/-cpUkuzpsPR6uUgWPkbH-A
*請認真填寫需求信息,我們會在24小時內與您取得聯系。