摘要:我打算分成前端魔法堂異常不僅僅是和前端魔法堂調(diào)用棧,異常實例中的寶藏兩篇分別敘述內(nèi)置自定義異常類,捕獲運行時異常語法異常網(wǎng)絡請求異常事件,什么是調(diào)用棧和如何獲取調(diào)用棧的相關信息。
前言
?編程時我們往往拿到的是業(yè)務流程正確的業(yè)務說明文檔或規(guī)范,但實際開發(fā)中卻布滿荊棘和例外情況,而這些例外中包含業(yè)務用例的例外,也包含技術上的例外。對于業(yè)務用例的例外我們別無它法,必須要求實施人員與用戶共同提供合理的解決方案;而技術上的例外,則必須由我們碼農(nóng)們手刃之,而這也是我想記錄的內(nèi)容。
?我打算分成《前端魔法堂——異常不僅僅是try/catch》和《前端魔法堂——調(diào)用棧,異常實例中的寶藏》兩篇分別敘述內(nèi)置/自定義異常類,捕獲運行時異常/語法異常/網(wǎng)絡請求異常/PromiseRejection事件,什么是調(diào)用棧和如何獲取調(diào)用棧的相關信息。
?是不是未出發(fā)就已經(jīng)很期待呢?好吧,大家捉緊扶手,老司機要開車了^_^
?本篇將敘述如下內(nèi)容:
異常還是錯誤?它會如何影響我們的代碼?
內(nèi)置異常類型有哪些?
動手寫自己的異常類型吧!
捕獲“同步代碼”中的"運行時異常",用try/catch就夠了。
"萬能"異常捕獲者window.onerror,真的萬能嗎?
Promise.reject也拋異常,怎么辦?
404等網(wǎng)絡請求異常真心要后之后覺嗎?
一.異常還是錯誤?它會如何影響我們的代碼??在學習Java時我們會被告知異常(Exception)和錯誤(Error)是不一樣的,異常是不會導致進程終止從而可以被修復(try/catch),但錯誤將會導致進程終止因此不能被修復。當對于JavaScript而言,我們要面對的僅僅有異常(雖然異常類名為Error或含Error字樣),異常的出現(xiàn)不會導致JavaScript引擎崩潰,最多就是讓當前執(zhí)行的任務終止而已。
?上面說到異常的出現(xiàn)最多就是讓當前執(zhí)行的任務終止,到底是什么意思呢?這里就涉及到Event Loop的原理了,下面我嘗試用代碼大致說明吧。
二.內(nèi)置異常類型有哪些?
?說到內(nèi)置異常類那么必先提到的就是Error這個祖先類型了,其他所有的內(nèi)置異常類和自定義類都必須繼承它。而它的標準屬性和方法就以下這寥寥幾個而已
@prop {String} name - 異常名稱 @prop {String} message - 供人類閱讀的異常信息 @prop {Function} constructor - 類型構造器 @method toString():String - 輸出異常信息
?由于標準屬性實在太少,無法提供更有效的信息供開發(fā)者定位異常發(fā)生的位置和重現(xiàn)事故現(xiàn)場,因此各瀏覽器廠家均手多多的自己增加些屬性,然后逐漸成了事實標準。
@prop {String} fileName - 異常發(fā)生的腳本URI @prop {number} lineNumber - 異常發(fā)生的行號 @prop {number} columnNumber - 異常發(fā)生的列號 @prop {String} stack - 異常發(fā)生時的調(diào)用棧信息,IE10及以上才支持 @method toSource():String - 異常發(fā)生的腳本內(nèi)容
另外巨硬還新增以下兩個屬性
@prop {String} description - 和message差不多 @prop {number} number - 異常類型的編號,巨硬為每個異常設置了一個唯一的編號
?那么現(xiàn)在我要實例化一個Error對象,只需調(diào)用Error()或new Error()即可;若想同時設置message,則改為Error("test")或new Error("test")。其實Error的構造函數(shù)簽名是這樣的
@constructor @param {String=} message - 設置message屬性 @param {String=} fileName - 設置fileName屬性 @param {number=} lineNumber - 設置lineNUmber屬性
現(xiàn)在我們看看具體有哪些內(nèi)置的異常類型吧!
EvalError,調(diào)用eval()時發(fā)生的異常,已被廢棄只用于向后兼容而已
InternalError,JavaScript引擎內(nèi)部異常,F(xiàn)ireFox獨門提供的!
RangeError,當函數(shù)實參越界時發(fā)生,如Array,Number.toExponential,Number.toFixed和Number.toPrecision時入?yún)⒎欠〞r。
ReferenceError,當引用未聲明的變量時發(fā)生
SyntaxError,解析時發(fā)生語法錯誤
TypeError,當值不是所期待的類型時,null.f()也報這個錯
URIError,當傳遞一個非法的URI給全局URI處理函數(shù)時發(fā)生,如decodeURIComponent("%"),即decodeURIComponent,decodeURI,encodeURIComponent,encodeURI
三.動手寫自己的異常類型吧!?關于在StackOverflow上早有人討論如何自定義異常類型了參考
于是我們順手拈來即可
function MyError(message, fileName, lineNumber){ if (this instanceof MyError);else return new MyError(message, fileName, lineNumber) this.message = message || "" if (fileName){ this.fileName = fileName } if (lineNumber){ this.lineNumber = lineNumber } } var proto = MyError.prototype = Object.create(Error.prototype) proto.name = "MyError" proto.constructor = MyError
cljs實現(xiàn)如下
(defn ^export MyError [& args] (this-as this (if (instance? MyError this) (let [ps ["message" "fileName" "lineNumber"] idxs (-> (min (count args) (count ps)) range)] (reduce (fn [accu i] (aset accu (nth ps i) (nth args i)) accu) this idxs)) (apply new MyError args)))) (def proto (aset MyError "prototype" (.create js/Object (.-prototype Error)))) (aset proto "name" "MyError") (aset proto "constructor" MyError)四.捕獲“同步代碼”中的"運行時異常",用try/catch就夠了
?為了防止由于異常的出現(xiàn),導致正常代碼被略過的風險,我們習慣采取try/catch來捕獲并處理異常。
try{ throw Error("unexpected operation happen...") } catch (e){ console.log(e.message) }
cljs寫法
(try (throw (Error. "unexpected operation happen...") (catch e (println (.-message e)))))
?很多時我們會以為這樣書寫就萬事大吉了,但其實try/catch能且僅能捕獲“同步代碼”中的"運行時異常"。
1."同步代碼"就是說無法獲取如setTimeout、Promise等異步代碼的異常,也就是說try/catch僅能捕獲當前任務的異常,setTimeout等異步代碼是在下一個EventLoop中執(zhí)行。
// 真心捕獲不到啊親~! try{ setTimeout(function(){ throw Error("unexpected operation happen...") }, 0) } catch(e){ console.log(e) }
2."運行時異常"是指非SyntaxError,也就是語法錯誤是無法捕獲的,因為在解析JavaScript源碼時就報錯了,還怎么捕獲呢~~
// 非法標識符a->b,真心捕獲不到啊親~! try{ a->b = 1 } catch(e){ console.log(e) }
?這時大家會急不可待地問:“異步代碼的異常咋辦呢?語法異常咋辦呢?”在解答上述疑問前,我們先偏離一下,稍微挖挖throw語句的特性。
throw后面可以跟什么啊??一般而言我們會throw一個Error或其子類的實例(如throw Error()),其實我們throw任何類型的數(shù)據(jù)(如throw 1,throw "test",throw true等)。但即使可以拋出任意類型的數(shù)據(jù),我們還是要堅持拋出Error或其子類的實例。這是為什么呢?
try{ throw "unexpected operation happen..." } catch(e){ console.log(e) } try{ throw TypeError("unexpected operation happen...") } catch(e){ if ("TypeError" == e.name){ // Do something1 } else if ("RangeError" == e.name){ // Do something2 } }
?原因顯然易見——異常發(fā)生時提供信息越全越好,更容易追蹤定位重現(xiàn)問題嘛!
五."萬能"異常捕獲者window.onerror,真的萬能嗎??在每個可能發(fā)生異常的地方都寫上try/catch顯然是不實際的(另外還存在性能問題),即使是羅嗦如Java我們開發(fā)時也就是不斷聲明throws,然后在頂層處理異常罷了。那么,JavaScript中對應的頂層異常處理入口又在哪呢?木有錯,就是在window.onerror??纯捶椒ê灻?/p>
@description window.onerror處理函數(shù) @param {string} message - 異常信息" @param {string} source - 發(fā)生異常的腳本的URI @param {number} lineno - 發(fā)生異常的腳本行號 @param {number} colno - 發(fā)生異常的腳本列號 @param {?Error} error - Error實例,Safari和IE10中沒有這個實參
?這時我們就可以通過它捕獲除了try/catch能捕獲的異常外,還可以捕獲setTimeout等的異步代碼異常,語法錯誤。
window.onerror = function(message, source, lineno, colno, error){ // Do something you like. } setTimeout(function(){ throw Error("oh no!") }, 0) a->b = 1
?這樣就滿足了嗎?還沒出大殺技呢——屏蔽異常、屏蔽、屏~~
?只有onerror函數(shù)返回true時,異常就不會繼續(xù)向上拋(否則繼續(xù)上拋就成了Uncaught Error了)。
// 有異常沒問題啊,因為我看不到^_^ window.onerror = function(){return true}
?現(xiàn)在回到標題的疑問中,有了onerror就可以捕獲所有異常了嗎?答案又是否定的(我的娘啊,還要折騰多久啊~0~)
Chrome中對于跨域腳本所報的異常,雖然onerror能夠捕獲,但統(tǒng)一報Script Error。若要得到正確的錯誤信息,則要配置跨域資源共享CORS才可以。
window.onerror實際上采用的事件冒泡的機制捕獲異常,并且在冒泡(bubble)階段時才觸發(fā),因此像網(wǎng)絡請求異常這些不會冒泡的異常是無法捕獲的。
Promise.reject產(chǎn)生的未被catch的異常,window.onerror也是無能為力。
六.Promise.reject也拋異常,怎么辦??通過Promise來處理復雜的異步流程控制讓我們得心應手,但倘若其中出現(xiàn)異常或Promise實例狀態(tài)變?yōu)閞ejected時,會是怎樣一個狀況,我們又可以如何處理呢?
Promise是如何標識異常發(fā)生的??Promise實例的初始化狀態(tài)是pending,而發(fā)生異常時則為rejected,而導致狀態(tài)從pending轉(zhuǎn)變?yōu)閞ejected的操作有
調(diào)用Promise.reject類方法
在工廠方法中調(diào)用reject方法
在工廠方法或then回調(diào)函數(shù)中拋異常
// 方式1 Promise.reject("anything you want") // 方式2 new Promise(function(resolve, reject) { reject("anything you want") }) // 方式3 new Promise(function{ throw "anything you want" }) new Promise(function(r) { r(Error("anything you want" ) }).then(function(e) { throw e })
?當Promise實例從pending轉(zhuǎn)變?yōu)閞ejected時,和之前談論到異常一樣,要么被捕獲處理,要么繼續(xù)拋出直到成為Uncaught(in promise) Error為止。
異常發(fā)生前就catch掉?若在異常發(fā)生前我們已經(jīng)調(diào)用catch方法來捕獲異常,那么則相安無事
new Promise(function(resolve, reject){ setTimeout(reject, 0) }).catch(function(e){ console.log("catch") return "bingo" }).then(function(x){ console.log(x) }) // 回顯 bingo專屬于Promise的頂層異常處理
?若在異常發(fā)生前我們沒有調(diào)用catch方法來捕獲異常,還是可以通過window的unhandledrejection事件捕獲異常的
window.addEventListener("unhandledrejection", function(e){ // Event新增屬性 // @prop {Promise} promise - 狀態(tài)為rejected的Promise實例 // @prop {String|Object} reason - 異常信息或rejected的內(nèi)容 // 會阻止異常繼續(xù)拋出,不讓Uncaught(in promise) Error產(chǎn)生 e.preventDefault() })遲來的catch
?由于Promise實例可異步訂閱其狀態(tài)變化,也就是可以異步注冊catch處理函數(shù),這時其實已經(jīng)拋出Uncaught(in promise) Error,但我們依然可以處理
var p = new Promise(function(resolve, reject){ setTimeout(reject, 0) }) setTimeout(function(){ p.catch(function(e){ console.log("catch") return "bingo" }) }, 1000)
?另外,還可以通過window的rejectionhandled事件監(jiān)聽異步注冊catch處理函數(shù)的行為
window.addEventListener("rejectionhandled", function(e){ // Event新增屬性 // @prop {Promise} promise - 狀態(tài)為rejected的Promise實例 // @prop {String|Object} reason - 異常信息或rejected的內(nèi)容 // Uncaught(in promise) Error已經(jīng)拋出,所以這句毫無意義^_^ e.preventDefault() })
注意:只有拋出Uncaught(in promise) Error后,異步catch才會觸發(fā)該事件。
七.404等網(wǎng)絡請求異常真心要后之后覺嗎??也許我們都遇到報404網(wǎng)絡請求異常的情況,然后測試或用戶保障怎么哪個哪個圖標沒有顯示。其實我們我們可以通過以下方式捕獲這類異常
window.addEventListener("error", function(e){ // Do something console.log(e.bubbles) // 回顯false }, true)
?由于網(wǎng)絡請求異常不會冒泡,因此必須在capture階段捕獲才可以。但還有一個問題是這種方式無法精確判斷異常的HTTP狀態(tài)是404還是500等,因此還是要配合服務端日志來排查分析才可以。
總結?對異常和如何捕獲異常僅僅是前端智能監(jiān)控中的一小撮知識點,敬請期待后續(xù)另一小撮知識點《前端魔法堂——調(diào)用棧,異常實例中的寶藏》吧:D
?尊重原創(chuàng),轉(zhuǎn)載請注明來自:http://www.cnblogs.com/fsjohn... ^_^肥仔John
https://developer.mozilla.org...
https://stackoverflow.com/que...
文章版權歸作者所有,未經(jīng)允許請勿轉(zhuǎn)載,若此文章存在違規(guī)行為,您可以聯(lián)系管理員刪除。
轉(zhuǎn)載請注明本文地址:http://systransis.cn/yun/89129.html
摘要:前言在上一篇前端魔法堂異常不僅僅是中我們描述出一副異常及如何捕獲異常的畫像,但僅僅如此而已。調(diào)用方從右到左的順序?qū)?shù)壓入棧中,在被調(diào)用方執(zhí)行完成后,由被調(diào)用方負責清理棧中的參數(shù)也稱為棧平衡。 前言 ?在上一篇《前端魔法堂——異常不僅僅是try/catch》中我們描述出一副異常及如何捕獲異常的畫像,但僅僅如此而已。試想一下,我們窮盡一切捕獲異常實例,然后僅僅為告訴用戶,運維和開發(fā)人員頁...
摘要:這樣很容易造成大的損失,提前做好錯誤收集和處理,可以減少損失。 編寫代碼只是做好項目的一小部分,寫代碼難免會碰到錯誤。因此,在項目上線后,我們還需要主動對項目的錯誤進行收集,不能等用戶發(fā)現(xiàn)錯誤,再聯(lián)系我們,我們再去處理。這樣很容易造成大的損失,提前做好錯誤收集和處理,可以減少損失。 本人并沒有做過相關的工作,下面的文章只是我在學習中的一點思考和總結,可能有比較多不足和錯誤的地方,希望大...
摘要:前端魔法堂異常不僅僅是在學習時我們會被告知異常和錯誤是不一樣的,異常是不會導致進程終止從而可以被修復,但錯誤將會導致進程終止因此不能被修復。 推薦 1. RESTful API 設計最佳實踐 https://blog.philipphauer.de/... 項目資源的URL應該如何設計?用名詞復數(shù)還是用名詞單數(shù)?一個資源需要多少個URL?用哪種HTTP方法來創(chuàng)建一個新的資源?可選參數(shù)應...
摘要:而同步和異步則是描述另一個方面。異步將數(shù)據(jù)從內(nèi)核空間拷貝到用戶空間的操作由系統(tǒng)自動處理,然后通知應用程序直接使用數(shù)據(jù)即可。 前言 ?上周5在公司作了關于JS異步編程模型的技術分享,可能是內(nèi)容太干的緣故吧,最后從大家的表情看出這條粉腸到底在說啥?的結果:(下面是PPT的講義,具體的PPT和示例代碼在https://github.com/fsjohnhuan...上,有興趣就上去看看吧! ...
閱讀 2919·2019-08-30 15:55
閱讀 2033·2019-08-30 14:02
閱讀 1311·2019-08-29 15:23
閱讀 1033·2019-08-29 11:27
閱讀 494·2019-08-26 11:43
閱讀 3221·2019-08-26 10:32
閱讀 1277·2019-08-23 14:41
閱讀 3323·2019-08-23 14:41