對異常的捕獲和處理是提高程序魯棒性的一個重要方式,即使在javascript/nodejs等看似“很難寫出bug”的弱類型語言里,異常捕獲處理仍至關重要,這主要是因為:
1.在一個代碼塊里,如果程序運行過程中自動、或主動(new Error/Exception)生成異常/錯誤后,若不主動去try...catch該異常,這個異常會逐層拋出,直至主程序,系統會按照框架默認方式處理該異常。
2.在逐層拋出異常的過程中,每層代碼塊異常點之后的程序不會再被執行,除非進行try...catch異常處理。
我們看幾個簡單的例子來驗證一下。
(() => { try{ (()=>{ nonExistentFunction(); console.log('c'); })(); console.log( 'b' ); }catch(e){ console.log( e ); } })(); console.log( 'a' );
打印結果:
即,打印了最外層的catch內的異常處理信息和主程序接下來的部分,根據1,2可知,如果匿名函數最外層沒有進行try...catch處理的話,a也不會被打印出來。
這意味着,即使是“很難寫出bug”的javascript也可能因為一個小小的異常導致整個程序歇菜!try...catch則是保證主干按流程執行完畢的關鍵實現。
容易被誤處理的異步異常
在nodejs等異步IO密集場景,經常用異步回調函數來處理IO操作結果——不管是正確的數據還是異常Error,但此時的try...catch怎么來寫?
const fs = require('fs'); try{ fs.readFile( __dirname+'/15_fs1.js',(err,data) =>{ if( err ){ throw err; }else{ console.log( data ); } } ); }catch(err){ console.log( 'an error occured!',err ); } console.log( 'hhh' );
在文件不存在的情況下,按照try..catch的作用,我們認為應該在catch里捕捉到異常並執行異常處理語句,即打印“an error occured!XXX”;但實際結果呢?
即,異常最后並未被目前縮寫的catch所捕獲,而是最終被系統級捕獲並按照框架方式打印出了錯誤信息,這是為什么呢?主要是因為try...catch是代碼塊,是被同步解析的,當代碼執行到try后,開始讀文件操作,等待異步執行結果,但catch語句是緊接着try進行的,它並不會等待異步執行的結果,因此,當執行到catch的時候,回調里的throw error還沒執行呢,當然catch不到了,主程序繼續解析執行直到打印出'hhh'。隨后當異步會調離throw err的時候沒有catch可以捕獲的到,只能層層拋出到最外層,由框架來捕獲和執行。
理解上述內容后,就該想到問題的關鍵點是try...catch的執行ticker與回調函數ticker不同步的問題,解決的辦法也很簡單,同步try...catch與callback函數的時鍾—將try..catch放在callback里面。
正確的代碼示例:
const fs = require('fs'); fs.readFile( __dirname+'/15_fs1.js',(err,data) =>{ try{ if( err ){ throw err; }else{ console.log( data ); } }catch(e){ console.log( e ); } } ); console.log( 'hhh' );
打印結果:
19年要在使用新框架寫好業務邏輯的基礎上,提高自己輸出代碼的魯棒性,經常去分析、反思可能出現異常的模塊並進行forecast error的捕獲處理,進一步提高自己的代碼水平。
——學無止境,保持好奇。May stars guide your way.