關於洋蔥模型不少人都理解,而且絕大多數人都知道要想保證洋蔥模型必需要使用async 和await node
那麼問題來了async和 await 是 用來解決異步編程的,那麼當咱們調用的下一個中間件不存在異步的時候,是否還須要使用async和 await編程
答案是確定的,以致於如今不少人只要是寫中間件必用async 和 await 那麼你是否知道它的運行機制和底層原理的 一個合格的開發人員是否是要作到知其然還要知其因此然呢?koa
咱們就拿最爲簡單的全局異常處理來舉例,在異步編程模型中全局異常處理由於函數執行時壓棧與出棧隊列的關係,每每有些抽象異步
result:5555async
上述代碼中fun2執行時後續執行棧中並無同步任務 異步編程
所以Promise中的異步任何將會執行而且拋出一個異常 函數
async fun2會返回一個Promise而且默認執行rejectoop
async fun1會處於出棧狀態而且接受到fun2的Promisespa
由於await的求值特性 因此會執行Promose中的reject再次將錯誤拋出 而且由fun1包裝爲Promise返回給fun線程
fun執行Promise中reject拋出錯誤,由於函數狀態隊列所有出棧,因此錯誤將被當即拋出 由try catch捕捉
那麼問題來了咱們能夠發現fun1中並無異步操做,它惟一作的一件事就是捕捉到了fun2返回Promise 而且再度將這個錯誤拋出 返回給了fun
那麼咱們爲何要給fun加await呢?讓fun1使用await讓它去和fun2壓棧很差嗎?
其實在這個案例中確實,fun中不使用await異步解決方案,並不影響咱們捕捉到這個錯誤
但回到問題的自己,咱們說的是要讓koa保證洋蔥模型,簡單修改下代碼
result:I do it first
result:5555
前文已經說過了async會將拋出的錯誤用一個Promise進行包裝,而await的求值特性會執行reject從而拋出錯誤
那麼修改後的代碼咱們再也不使用await,就須要手動catch這個錯誤了,但這並不影響它自己捕捉這個錯誤不是嗎?
但若是你是一個koa玩家,這個時候大機率必定明白了,是的
洋蔥模型要保證執行順序,在fun1,fun2所有執行完畢後才能夠調用I do it first
可是很明顯結果並非,這就是爲何即使下一個中間件不存在異步編程咱們也要使用async的緣由
至於緣由,是由於node畢竟是基於V8引擎,而其自己單線程的特性,在遇到await時會對其所在的函數進行壓棧先去執行執行棧中的同步任務
這也是應有之意吧,因此在咱們給fun加上await以後,咱們就強制要求它進行壓棧,必須等fun2 fun1處理完畢後再行出棧
那麼也就保證了洋蔥模型,至於node的運行原理,event loop 事件隊列這一些,我相信是一個JS玩家的基本素質了吧
好的那麼再加一層吧,看看能不能捕捉到,是的捕捉到了依然是層層執行,拋出錯誤,包裝爲Promise,是否是頗有魅力呢?可是問題依然不能保證洋蔥模型
async function fun() { add().catch((err) => { console.log(55555); }); console.log('I do it first'); } async function add() { await fun1(); } async function fun1() { await fun2(); } async function fun2() { await new Promise((resolve, reject) => { setTimeout(() => { reject(new Error('error')); }, 0); }); } fun();
咱們看到代碼中除了fun2中存在異步編程其餘都沒有,可是必需要使用async和await才能保證錯誤層層遞進,固然了函數出棧時自己是不受錯誤影響的
但有一個問題請不要忽略,那就是若是你不對當前函數進行壓棧,那麼當它執行到fun2時發現是異步編程對其壓棧
那麼fun1和add不進行壓棧那麼它們就會瞬間執行完畢,從而你在對fun進行壓棧它依然捕捉不到錯誤
由於當add執行完畢時,fun就已經出棧了,可是add並無捕捉到錯誤,從而也就不可能拋出錯誤了好的再次修改代碼舉下例
async function fun() { try { await add(); } catch (error) { console.log(2222); } console.log('I do it first'); } async function add() { fun1(); } async function fun1() { await fun2(); } async function fun2() { await new Promise((resolve, reject) => { setTimeout(() => { reject(new Error('error')); }, 0); }); } fun();
這個時候add並無使用await進行壓棧,那麼咱們來推演一下執行過程,fun調用add,add調用fun1
fun1調用fun2,當執行棧到這裏時,發現存在await異步編程,對fun2進行壓棧,而且返回一個pending狀態的Promise
此時fun1由於fun2壓棧也會進行壓棧,而且再度拋出pending狀態的Promise,這個時候add拿到了fun1拋出的Promise
若是這個時候對add拿到的Promise就行catch那麼將會fun2出棧時會執行reject拋出錯誤,fun1所以也就能拿到錯誤而且執行
這個時候add天然可以攔截錯誤,但問題是咱們須要讓fun攔截錯誤,因此又回到了問題自己必須給使用await對add強行壓棧