起因
在之前的專案中我發現每個控制器大約都是這樣寫的
async function findId (id) {
let res;
try{
res = await document.findById(id);
}catch(e){
return false;
}
}
當然這麼寫其實沒有任何問題,甚至是一個很好的習慣,他保證了錯誤可以被正常的捕獲,另外可以在catch裡隨便做處理,比如console,錯誤日誌。一切都看起來那麼完美
轉折
那麼假設現在有一個開發就是那麼剛,寫了幾十個控制器,然後都沒加try catch,理直氣壯的說,這麼多重複程式碼真的不能抽象一下統一處理嗎。
好吧且不說抽象統一處理的事,解決問題才是目的。
解決吧,他剛我也剛,每個控制器加上不就是了。。。呵呵噠,邊罵邊加,我想半天也差不多了。
這樣真的好嗎,萬一現在在catch這需要根據e做一些特殊處理呢或者增加一個功能呢?egg的話有一個統一的errorhandle可以配置。確實把整個錯誤處理可以完整的抽象出來。
面向切面程式設計
這裡的話要提一個java大大朋友告訴我的spring的解決手段,面向切面程式設計,具體概念網上解釋很多,大約可以理解為,在所有控制器前,或者說控制器和服務之間,這樣的話就可以實現很多公共邏輯的抽象。
面向切面程式設計適合做的事,這個spring的實踐實在太多我就不贅述了,錯誤異常處理,日誌,許可權,這些在切面上的實踐都是很好的。
切面與中介軟體
其實中介軟體我認為也屬於一個切面,這個切面是在請求上下文中的,但是這個切面無法覆蓋所有的要求,特別當你需要控制的功能不是以請求為粒度控制的時候,或者說僅僅需要對服務層的服務新增功能的時候。
實踐
這裡就以開頭的那段程式碼的簡化為例。首先是一個包裝函式,用來包裝所有的控制器的方法
function controllerWrap(controllerFn, self) {
return async function() {
let that = self;
try {
await controllerFn.apply(that, arguments);
} catch(e) {
throw(e);
}
};
}
其實注意點也就兩個,注意上下文,然後是保證controllerFn的arguments不要丟了,其實也就是ctx和next的
在把所有的控制器例項化後(一般在都會把控制器形成單例模式,之後只要遍歷這些單例中的方法包裹控制器函式就可以了)。當然可以在wrap中注入更多統一處理函式,也可以通過函式名做一些判斷,比如對所有名字中包含save的函式進行單獨的日誌處理。
egg.js的實踐
egg在實踐上是不太一致,因為控制器並非簡單的例項化,如果需要對egg的控制器的進行包裝則需要在loader中進行包裝。
參考:https://eggjs.org/zh-cn/advan…
之後會嘗試根據現有的業務沉澱出一套egg之上的框架。