摘要:提供一套錯誤處理機制,錯誤是干擾程序正常流程的非正常的事故。構(gòu)造函數(shù)是通用錯誤類型,除了類型,還有等類型。瀏覽器輸出其他錯誤類型構(gòu)造函數(shù)是繼承,實例是一致的。數(shù)值超出有效范圍數(shù)值超出有效范圍創(chuàng)建一個實例,表示錯誤的原因無效引用。
同步發(fā)布于 https://github.com/xianshanna...
我的建議是不要隱藏錯誤,勇敢地拋出來。沒有人會因為代碼出現(xiàn) bug 導致程序崩潰而羞恥,我們可以讓程序中斷,讓用戶重來。錯誤是無法避免的,如何去處理它才是最重要的。
JavaScript 提供一套錯誤處理機制,錯誤是干擾程序正常流程的非正常的事故。而沒人可以保持程序沒有 bug,那么上線后遇到特殊的 bug,如何更快的定位問題所在呢?這就是我們這個專題需要討論的問題。
下面會從 JavaScript Error 基礎知識、如何攔截和捕獲異常、如何方便的在線上報錯誤等方面來敘述,本人也是根據(jù)網(wǎng)上的知識點進行了一些總結(jié)和分析(我只是互聯(lián)網(wǎng)的搬運工,不是創(chuàng)造者),如果有什么錯漏的情況,請在 issue 上狠狠的批評我。
這個專題目前是針對瀏覽器的,還沒考慮到 node.js,不過都是 JavaScript Es6 語法,大同小異。
什么時候 JavaScript 會拋出錯誤呢?一般分為兩種情況:
JavaScript 自帶錯誤
開發(fā)者主動拋出的錯誤
JavaScript 引擎自動拋出的錯誤大多數(shù)場景下我們遇到的錯誤都是這類錯誤。如果發(fā)生Javscript 語法錯誤、代碼引用錯誤、類型錯誤等,JavaScript 引擎就會自動觸發(fā)此類錯誤。如下一些場景:
場景一
console.log(a.notExited) // 瀏覽器會拋出 Uncaught ReferenceError: a is not defined
場景二
const a; // 瀏覽器拋出 Uncaught SyntaxError: Missing initializer in const declaration
語法錯誤,瀏覽器一般第一時間就拋出錯誤,不會等到執(zhí)行的時候才會報錯。
場景三
let data; data.forEach(v=>{}) // Uncaught TypeError: Cannot read property "forEach" of undefined手動拋出的錯誤
一般都是類庫開發(fā)的自定義錯誤異常(如參數(shù)等不合法的錯誤異常拋出)?;蛘咧匦滦薷腻e誤 message 進行上報,以方便理解。
場景一
function sum(a,b){ if(typeof a !== "number") { throw TypeError("Expected a to be a number."); } if(typeof b !== "number") { throw TypeError("Expected b to be a number."); } return a + b; } sum(3,"d"); // 瀏覽器拋出 uncaught TypeError: Expected b to be a number.
場景二
當然我們不一定需要這樣做。
let data; try { data.forEach(v => {}); } catch (error) { error.message = "data 沒有定義,data 必須是數(shù)組。"; error.name = "DataTypeError"; throw error; }如何創(chuàng)建 Error 對象?
創(chuàng)建語法如下:
new Error([message[,fileName,lineNumber]])
省略 new 語法也一樣。
其中fileName 和 lineNumber 不是所有瀏覽器都兼容的,谷歌也不支持,所以可以忽略。
Error 構(gòu)造函數(shù)是通用錯誤類型,除了 Error 類型,還有 TypeError、RangeError 等類型。
Error 實例這里列舉的都是 Error 層的原型鏈屬性和方法,更深層的原型鏈的繼承屬性和方便不做說明。一些有兼容性的而且不常用的屬性和方法不做說明。
console.log(Error.prototype) // 瀏覽器輸出 {constructor: ?, name: "Error", message: "", toString: ?}
其他錯誤類型構(gòu)造函數(shù)是繼承 Error,實例是一致的。
屬性Error.prototype.message
錯誤信息, Error("msg").message === "msg"。
Error.prototype.name
錯誤類型(名字), Error("msg").name === "Error”。如果是 TypeError,那么 name 為 TypeError。
Error.prototype.stack
Error 對象作為一個非標準的棧屬性提供了一種函數(shù)追蹤方式。無論這個函數(shù)被被調(diào)用,處于什么模式,來自于哪一行或者哪個文件,有著什么樣的參數(shù)。這個棧產(chǎn)生于最近一次調(diào)用最早的那次調(diào)用,返回原始的全局作用域調(diào)用。
這個不是規(guī)范,存在兼容性。經(jīng)測試,谷歌、火狐、Edge、safar 都支持此特性(都是在最新的版本下測試 2019-04-02),IE 不支持。
方法Error.prototype.constructor
Error.prototype.toString
返回值格式為 ${name}: ${message}。
常用 Error 類型除了通用的 Error 構(gòu)造函數(shù)外,JavaScript還有常見的 5 個其他類型的錯誤構(gòu)造函數(shù)。
TypeError創(chuàng)建一個 Error 實例,表示錯誤的原因:變量或參數(shù)不屬于有效類型。
throw TypeError("類型錯誤"); // Uncaught TypeError: 類型錯誤RangeError
創(chuàng)建一個 Error 實例,表示錯誤的原因:數(shù)值變量或參數(shù)超出其有效范圍。
throw RangeError("數(shù)值超出有效范圍"); // Uncaught RangeError: 數(shù)值超出有效范圍ReferenceError
創(chuàng)建一個 Error 實例,表示錯誤的原因:無效引用。
throw ReferenceError("無效引用"); // Uncaught ReferenceError: 無效引用SyntaxError
創(chuàng)建一個 Error 實例,表示錯誤的原因:語法錯誤。這種場景很少用,除非類庫定義了新語法(如模板語法)。
throw SyntaxError("語法錯誤"); // Uncaught SyntaxError: 語法錯誤URIError
創(chuàng)建一個 Error 實例,表示錯誤的原因:涉及到 uri 相關(guān)的錯誤。
throw URIError("url 不合法"); // Uncaught RangeError: url 不合法自定義 Error 類型
自定義新的 Error 類型需要繼承 Error ,如下自定義 CustomError:
function CustomError(...args){ class InnerCustomError extends Error { name = "CustomError"; } return new InnerCustomError(...args); }
繼承 Error 后,我們只需要對 name 做重寫,然后封裝成可直接調(diào)用的函數(shù)即可。
如何攔截 JavaScript 錯誤?既然沒人能保證 web 應用不會出現(xiàn) bug,那么出現(xiàn)異常報錯時,如何攔截并進行一些操作呢?
try…catch… 攔截這是攔截 JavaScript 錯誤,攔截后,如果不手動拋出錯誤,這個錯誤將靜默處理。平常寫代碼如果我們知道某段代碼可能會出現(xiàn)報錯問題,就可以使用這種方式。如下:
const { data } = this.props; try { data.forEach(d=>{}); // 如果 data 不是數(shù)組就會報錯 } catch(err){ console.error(err); // 這里可以做上報處理等操作 }一些使用方式
try...catch... 使用需要注意,try…catch… 后,錯誤會被攔截,如果不主動拋出錯誤,那么無法知道報錯位置。如下面的處理方式就是不好的。
function badHandler(fn) { try { return fn(); } catch (err) { /**noop,不做任何處理**/ } return null; } badHandler();
這樣 fn 回調(diào)發(fā)送錯誤后,我們無法知道錯誤是在哪里發(fā)生的,因為已經(jīng)被 try…catch 了,那么如何解決這個問題呢?
function CustomError(...args){ class InnerCustomError extends Error { name = "CustomError"; } return new InnerCustomError(...args); } function uglyHandlerImproved(fn) { try { return fn(); } catch (err) { throw new CustomError(err.message); } return null; } badHandler();
現(xiàn)在,這個自定義的錯誤對象包含了原本錯誤的信息,因此變得更加有用。但是因為再度拋出來,依然是未處理的錯誤。
try…catch… 可以攔截異步錯誤嗎?這個也要分場景,也看個人的理解方向,首先理解下面這句話:
try…catch 只會攔截當前執(zhí)行環(huán)境的錯誤,try 塊中的異步已經(jīng)脫離了當前的執(zhí)行環(huán)境,所以 try…catch… 無效。
setTimeout 和 Promise 都無法通過 try…catch 捕獲到錯誤,指的是 try 包含異步(非當前執(zhí)行環(huán)境),不是異步包含 try(當前執(zhí)行環(huán)境)。異步無效和有效 try…catch 如下:
setTimeout
這個無效:
try { setTimeout(() => { data.forEach(d => {}); }); } catch (err) { console.log("這里不會運行"); }
下面的 try…catch 才會有效:
setTimeout(() => { try { data.forEach(d => {}); } catch (err) { console.log("這里會運行"); } });
Promise
這個無效:
try { new Promise(resolve => { data.forEach(d => {}); resolve(); }); } catch (err) { console.log("這里不會運行"); }
下面的 try…catch 才會有效:
new Promise(resolve => { try { data.forEach(d => {}); } catch (err) { console.log("這里會運行"); } });小結(jié)
不是所有場景都需要 try…catch… 的,如果所有需要的地方都 try…catch,那么代碼將變得臃腫,可讀性變差,開發(fā)效率變低。那么我需要統(tǒng)一獲取錯誤信息呢?有沒有更好的處理方式?當然有,后續(xù)會提到。
Promise 錯誤攔截Promise.prototype.catch 可以達到 try…catch 一樣的效果,只要是在 Promise 相關(guān)的處理中報錯,都會被 catch 到。當然如果你在相關(guān)回調(diào)函數(shù)中 try…catch,然后做了靜默提示,那么也是 catch 不到的。
如下會被 catch 到:
new Promise(resolve => { data.forEach(v => {}); }).catch(err=>{/*這里會運行*/})
下面的不會被 catch 到:
new Promise(resolve => { try { data.forEach(v => {}); }catch(err){} }).catch(err=>{/*這里不會運行*/})
Promise 錯誤攔截,這里就不詳細說了,如果你看懂了 try…catch,這個也很好理解。
setTimeout 等其他異步錯誤攔截呢?目前沒有相關(guān)的方式直接攔截 setTimeout 等其他異步操作。
如果要攔截 setTimeout 等異步錯誤,我們需要在異步回調(diào)代碼中處理,如:
setTimeout(() => { try { data.forEach(d => {}); } catch (err) { console.log("這里會運行"); } });
這樣可以攔截到 setTimeout 回調(diào)發(fā)生的錯誤,但是如果是下面這樣 try…catch 是無效的:
try { setTimeout(() => { data.forEach(d => {}); }); } catch (err) { console.log("這里不會運行"); }如何獲取 JavaScript 錯誤信息?
你可以使用上面攔截錯誤信息的方式獲取到錯誤信息。但是呢,你要每個場景都要去攔截一遍嗎?首先我們不確定什么地方會發(fā)生錯誤,然后我們也不可能每個地方都去攔截錯誤。
不用擔心,JavaScript 也考慮到了這一點,提供了一些便捷的獲取方式(不是攔截,錯誤還是會終止程序的運行,除非主動攔截了)。
window.onerror 事件獲取錯誤信息onerror 事件無論是異步還是非異步錯誤(除了 Promise 錯誤),onerror 都能捕獲到運行時錯誤。
需要注意一下幾點:
window.onerror 函數(shù)只有在返回 true 的時候,異常才不會向上拋出,否則即使是知道異常的發(fā)生控制臺還是會顯示 Uncaught Error: xxxxx。如果使用 addEventListener,event.preventDefault() 可以達到同樣的效果。
window.onerror 是無法捕獲到網(wǎng)絡異常的錯誤、或資源加載失敗等錯誤。不過如果你使用了 fetch 等支持 promise 的方式,錯誤可以通過 unhandledrejection 方式拿到錯誤信息。
語法:
window.onerror
window.onerror = function(message, source, lineno, colno, error) { ... }
window.addEventListener("error")
window.addEventListener("error", function(event) { ... })
詳細看這里。
unhandledrejection 事件獲取 Promise 錯誤unhandledrejection 存在兼容性問題,IE、Edge、火狐等目前都不支持。
用法如下:
window.addEventListener("unhandledrejection", event => { console.warn(`UNHANDLED PROMISE REJECTION: ${event.reason}`); });
或者
window.onunhandledrejection = event => { console.warn(`UNHANDLED PROMISE REJECTION: ${event.reason}`); };
詳細看這里。
如何處理錯誤信息?錯誤對象 Error 包含如下的字段返回:
name
錯誤的類型,一般有 Error、TypeError、ReferenceError、RangeError、URIError 等,當然也可以是自定義的錯誤類型。
message
錯誤的詳細信息,可以是 JavaScript 自動拋出的錯誤信息,也可以是手動拋出的自定義信息。
stack
這個不是規(guī)范,存在兼容性。經(jīng)測試,谷歌、火狐、Edge、safar 都支持此特性(都是在最新的版本下測試 2019-04-02),IE 不支持。
name 和 message 我們都不用做什么處理,主要是要針對 stack 做處理,一般我們需要把,這三個字段的信息提交到錯誤處理系統(tǒng),針對性處理。
同時我們生成環(huán)境的代碼是被壓縮后的代碼,需要使用 sourceMap 進行映射還原代碼。
后續(xù)會補充這個討論。
參考文章Error(MDN)
A Guide to Proper Error Handling in JavaScript
Anatomy of a JavaScript Error
文章版權(quán)歸作者所有,未經(jīng)允許請勿轉(zhuǎn)載,若此文章存在違規(guī)行為,您可以聯(lián)系管理員刪除。
轉(zhuǎn)載請注明本文地址:http://specialneedsforspecialkids.com/yun/103193.html
摘要:后來當其他地方出現(xiàn)了詭異的,定位問題時才發(fā)現(xiàn)這里的寫法嚴重不對。問題分析可以肯定問題出在異常捕獲而不是拋出。問題解決由于是異常捕獲時候的錯誤所以我們在捕獲的時候這樣處理。至此,問題解決。 問題復現(xiàn) 在工作時遇到了需要拋出異常并且需要自己捕獲處理的地方,于是在拋出的地方寫下 function parseExcel(con) { try { // doSomethin...
摘要:函數(shù)會在之后的某個時刻觸發(fā)事件定時器。事件循環(huán)中的這樣一次遍歷被稱為一個。執(zhí)行完畢并出棧。當定時器過期,宿主環(huán)境會把回調(diào)函數(shù)添加至事件循環(huán)隊列中,然后,在未來的某個取出并執(zhí)行該事件。 原文請查閱這里,略有改動。 本系列持續(xù)更新中,Github 地址請查閱這里。 這是 JavaScript 工作原理的第四章。 現(xiàn)在,我們將會通過回顧單線程環(huán)境下編程的弊端及如何克服這些困難以創(chuàng)建令人驚嘆...
摘要:與此同時,后端服務的中也有相關(guān)的長連接維持時長設置。如何快速的恢復連接根據(jù)上面的操作方案,我們會在網(wǎng)絡異常時斷開連接。 概述 通過前四篇博客,相信讀者對于WebSocket的使用和數(shù)據(jù)(不論是ArrayBuffer還是String)傳輸都有了一個深刻的了解?,F(xiàn)在我們來介紹下,我在使用WebSocket時,連接相關(guān)模塊遇到的一些共性問題,以及我們?nèi)绾谓鉀Q這些問題。 本文作為WebSock...
閱讀 3694·2021-11-22 15:24
閱讀 1599·2021-09-26 09:46
閱讀 1912·2021-09-14 18:01
閱讀 2610·2019-08-30 15:45
閱讀 3528·2019-08-30 14:23
閱讀 1874·2019-08-30 12:43
閱讀 2917·2019-08-30 10:56
閱讀 803·2019-08-29 12:20