從早期從事基於java的服務器端開發,再到以後從事基於web和js的ui開發,整體感受基於web頁面的ui開發遠不如服務器端健壯。主要是早期ie瀏覽器功能太弱小,不少業務被迫放到服務器端去實現,瀏覽器端技術設計的比較簡單。其次,js這門語言對於異常裏也不夠完善,沒有一套足夠完善的異常處理思路,儘管js存在throw、try、catch這種異常的語法,可是js自己是一門函數式編程的語言,try、catch在不少回調場景中是那麼無力。前端
因此一直以來我就想構建出一個完善的js異常處理機制,並且還要基於具體ui實際需求,作出具體的處理邏輯。簡單來講,當boss問咱們作這種統一的異常處理有什麼好處時,做爲前端工程師,最理想的回答就是增長用戶體驗,做爲前段工程師,用戶體驗是咱們最大的價值,由於只有增長用戶體驗,公司才能留住用戶,保持盈利,咱們才能發揮出價值。那麼如何提高用戶體驗呢?當用戶遇到了不一樣的錯誤狀況,使用最適合的提示方式提供給用戶,不就是增長了用戶的使用體驗嗎?java
因此統一的異常處理的目的就是,可以使用簡單、嚴謹的思路讓程序員開發出更容易維護的代碼,增長前端js代碼的健壯性,同時將異常處理的提示具體實現和業務邏輯解耦,由統一的提示接口去將處理異常信息。程序員
那麼這咱們提出設計方案以前,先來分析一下傳統的異常處理方法,以及js中使用時候的問題,也來想一想爲何咱們平時在js中使用異常處理的狀況是那麼少。web
傳統的異常處理理論,就是一個責任鏈模式,當方法A調用方法B,方法B調用方法C,其中每個方法其實均可能拋出異常,可能處理異常。ajax
function f(){ try{ } catch(e){ //什麼也不作 throw e; } } //等效於 function f(){}
自己方法調用就是一個鏈,而異常處理則處於這個鏈上。當一個方法拋出一個異常時,他本也是異常的第一個接收人(catch到異常),若是是他職責身份能處理的異常,他就應該馬上處理此異常,再也不向上拋出;不然就應該向上拋出,拋到上一層方法。依次類推,直到拋到最頂層。一般這個最頂層是一個用戶或者系統調用接口,這個接口做爲方法調用的發起者,同時也應該是異常處理的最終響應者,在這一層咱們將真正地去處理底層方法沒法處理的異常。編程
如何判斷一個異常是一個方法可否處理的呢?依據「最小知道原則」和「職責單一原則」,一個異常若是屬於其業務範圍內的一部分,就應該處理這個異常,若是須要外界知曉這個異常存在,就應該將這個異常加工後拋到上一層。api
function fa(){ try{ fb(); }catch(e){ if(e == "方法a能處理的異常"){ console.log("方法a處理了異常") } else { console.log("方法a沒法處理此異常,繼續向上拋出") throw e; } } } function fb(){ try{ fc(); }catch(e){ if(e == "方法b能處理的異常"){ console.log("方法b處理了異常") } else { console.log("方法b沒法處理此異常,繼續向上拋出") throw e; } } } function fc(){ try{ throw "方法acb都不能處理此異常"; //throw "方法a能處理的異常"; //throw "方法b能處理的異常"; //throw "方法c能處理的異常"; }catch(e){ if(e == "方法c能處理的異常"){ console.log("方法c處理了異常") } else { console.log("方法c沒法處理此異常,繼續向上拋出") throw e; } } } (function(){ try{ fa() }catch(e){ console.log("最頂層處理了此異常"); } })()
其中a、b、c中若是沒有能夠處理的異常,try、catch語句是能夠省略掉的,這樣代碼就會簡寫爲瀏覽器
function fa(){ fb(); } function fb(){ fc(); } function fc(){ throw "方法acb都不能處理此異常"; } (function(){ try{ fa() }catch(e){ console.log("最頂層處理了此異常"); } })()
是否是簡單了很多,咱們實際開發中更多的是這種例子,由於一個函數出現錯誤,後續執行都將沒法正常進行,因此是須要向上層拋出的。所以js語法中的異常處理策略簡化了整個過程。服務器
因此說,傳統的異常處理就是一個責任鏈模式。然而js中真的就可使用這種責任鏈模式的異常處理嗎?爲何咱們在開發js中,不多采用這種責任鏈模式的異常處理呢?接下來咱們繼續介紹js的異步調用。前端工程師
js是個很神奇的語言,用這個語言你能夠像用c語言那樣在全局變量裏,面向過程地編寫你的代碼,也能夠像使用java那種,面向對象地編寫代碼,他還可使用現狀漸漸開始火起來在函數式的方法編寫代碼。
在js裏面,函數能夠向變量同樣被聲明,能夠作方法的入參,能夠作方法的返回值,這些都是js不可或缺的語法特性。
由於js還有沒有語法級的阻塞方案,這樣你沒法同步兩個不一樣的線程可以同步彼此,例如一個異步請求,或者調用。當調用一個異步方法,由於沒有語法級的阻塞方式,因此整個調用過程當中,你沒法順序地編寫調用過程,惟一能作的事情只有回調。
這些語法特性使得咱們開發的時候,只能作到基於函數回調狀況去處理不一樣的情況,而責任鏈模式的異常處理也變得再也不適用。
function f(){ throw "error"; } try{ setTimeout(f) } catch(e){ console.log(e) //接收不到這個error的 }
這種狀況在實際開發中會常常遇到。最多見的就是一個ajax的異步請求,除此以外,異步io的api、地理定位的api、攝像機的api,這些都是異步的。再好比咱們模擬一個alert方法,不使用系統的alert而是咱們本身的alert(系統的alert函數調用後會彈出真正的模式對話框,此時線程掛起,運行阻塞,當用戶點掉alert對話框後線程纔會喚起)。與系統alert不同,咱們本身作的alert只能在回調里加alert後續的業務方法。
//使用Window對象的alert alert("我被阻塞了"); console.log("執行完畢"); var myAlert = { show: function(fn) { //繪製alert對話框略 //監聽用戶點擊事件,點擊後回調fn函數 var body = document.querySelector("body"); var _fn = function() {
//去除對話框略 body.removeEventListener("click", _fn) fn && fn(); } body.addEventListener("click", _fn, false); } } //調用自定義的alert myAlert.show(function() { console.log("執行完畢"); })
這種回調在js程序設計中經常被用到,由於這種方式是不支持責任鏈模式的,因此try、catch這種異常處理的語法在這種調用中不多會被使用到。那麼這種基於回調的調用過程,通常使用什麼方法作異常處理呢?一般函數自己會有一個錯誤的回調入參,參數要求是一個咱們仍是使用a、b、c這三個方法彼此調用來講明。
function fa(error){ var errorFn = function(e){ if(e == "方法a能處理的異常"){ console.log("方法a處理了異常") } else { console.log("方法a沒法處理此異常,繼續向上拋出") error && error(e); } } setTimeout(function(){ fb(errorFn); }) } function fb(error){ var errorFn = function(e){ if(e == "方法b能處理的異常"){ console.log("方法b處理了異常") } else { console.log("方法b沒法處理此異常,繼續向上拋出") error && error(e); } } setTimeout(function(){ fc(errorFn); }) } function fc(error){ var errorFn = function(e){ if(e == "方法c能處理的異常"){ console.log("方法c處理了異常") } else { console.log("方法c沒法處理此異常,繼續向上拋出") error && error(e); } } setTimeout(function(){ try{ throw "方法acb都不能處理此異常"; //throw "方法a能處理的異常"; //throw "方法b能處理的異常"; //throw "方法c能處理的異常"; }catch(e){ errorFn(e); } }) }
fa(function(){
console.log("最頂層處理了此異常");
})
注意這裏的errorFn是不能夠省略的,由於這個責任鏈模式是咱們手動書寫出來的。因此要想實現異步過程的責任鏈模式,是必須經過本身手動完成的,js並無提供什麼語法糖幫咱們簡化這個過程(其實也是有的)。
並且,只有最裏層的fc當中使用try、catch語句,fb、fa都沒法再使用try、catch語句了,由於一旦使用了這種回調方案,就再也沒法迴歸傳統的try、catch處理了,這也是try、catch語法沒法在js裏面流行的一大緣由。
這裏基本分析出了傳統js代碼在異常處理方面的方案和出現的問題,如何更好地解決異常處理問題,提供咱們程序的健壯性咱們值得進一步思考,待續。。。。