Koa 的非同步流程設計
Koa 採用了 Generator 來控制非同步流程,所以在採用 Koa 的程式碼上,我們會看到很多 yield 關鍵字的出現。某程度來說,Generator 讓程式碼變得乾淨許多,也平坦許多,比較少出現太複雜的 Callback 高山。這對 JavaScript 來說是件不錯的事,只是,對很多開發者來說,需要點時間熟悉其概念。
總而言之,所有 yield 的關鍵字,一定只會出現在 Generator 當中,而一個 Generator 長得會是一個以『*』符號開頭的函數模樣:
所以若是你仔細觀察,就會發現 Koa 的路由處理,採用的不是 callback 來處理客戶端要求,而是 Generator:
其實不久前,舊文『如何於 KOA 實作長輪詢(LONG-POLLING)機制』已提及怎麼來掌控 Koa 的流程,雖然只是簡單提及,但已經大略展示其使用方法。簡單來說,你可以當作這個 Generator 結束後,與客戶端的連線就會結束,所以我們如果要處理非同步的工作,也必須避免和阻止這個 Generator 執行完。
如下面範例就是想要執行一個非同步函數 setTimeout(),並等待其執行完成。在這範例中,我們加上了 console.log() 印出 START 和 END 字串,便於觀察其行為:
依照以上的例子,如果你用瀏覽器發送要求後,可以在 Server 端馬上看見 START 被印出來,然後經過一秒後會先出現 TIMEOUT 再出現 END。
很多人可能會覺得頭痛,因為照過去 JavaScript 的非同步概念來看,順序應該是要 START、END 然後等待一秒鐘後才是 TIMEOUT。但在 Generator 中,使用了 yield 以後並不是如此,所以你可以把它看作是一個在 JavaScript 中的特殊領域,以同步化(Synchronous)會阻塞(Block)的概念來進行邏輯設計的存在。
Koa 的設計上,只要將錯誤代入到 callback 的第一個參數,使第一個參數不是 null,就會拋出錯誤,讓 yield 外的 try-catch 去攔截,如此就可以很容易來處理非同步機制的錯誤。
在 Koa 中,確實作法只需要代入想要回傳的資料到 callback 的第二個參數即可:
如果你很熟悉 JavaScript 的開發,應該已經知道怎麼做,邏輯如前面所提及的範例一樣,只是額外進行一層包裝罷了:
所以,如果你是一個 Generator 的初學者,建議先不要直接學習原始 Generator 的使用方法,可以藉由 Koa 先熟悉並瞭解 Generator 的邏輯。等到都熟悉了 Generator 的概念後,再開始學習怎麼自行實作如同 Koa 的 callback。
總而言之,所有 yield 的關鍵字,一定只會出現在 Generator 當中,而一個 Generator 長得會是一個以『*』符號開頭的函數模樣:
function *() { // 工作流程 }
所以若是你仔細觀察,就會發現 Koa 的路由處理,採用的不是 callback 來處理客戶端要求,而是 Generator:
router.get('/test', function *() { // 處理客戶端要求的工作 });
其實不久前,舊文『如何於 KOA 實作長輪詢(LONG-POLLING)機制』已提及怎麼來掌控 Koa 的流程,雖然只是簡單提及,但已經大略展示其使用方法。簡單來說,你可以當作這個 Generator 結束後,與客戶端的連線就會結束,所以我們如果要處理非同步的工作,也必須避免和阻止這個 Generator 執行完。
使用 yield 控制 Generator 的流程
為了阻止 Generator 一下就跑完,我們會使用 yield 方法來暫停 Generator 的執行,並等待一個需要花時間的非同步工作完成。如果你對舊文的內容有印象就會知道,想要使用 yield 在 Koa 中去呼叫一個非同步機制,就需要設計一個特別的函數,其函數有一個 callback,當這個 callback 被呼叫時,代表工作完成。如下面範例就是想要執行一個非同步函數 setTimeout(),並等待其執行完成。在這範例中,我們加上了 console.log() 印出 START 和 END 字串,便於觀察其行為:
router.get('/test', function *() { console.log('START'); // 暫停 Generator 並等待工作完成 yield function(done) { setTimeout(function() { console.log('TIMEOUT'); // 完成,呼叫 callback 告訴 Generator 可以繼續 yield 以後的工作 done(); }, 1000); }; console.log('END'); });
依照以上的例子,如果你用瀏覽器發送要求後,可以在 Server 端馬上看見 START 被印出來,然後經過一秒後會先出現 TIMEOUT 再出現 END。
很多人可能會覺得頭痛,因為照過去 JavaScript 的非同步概念來看,順序應該是要 START、END 然後等待一秒鐘後才是 TIMEOUT。但在 Generator 中,使用了 yield 以後並不是如此,所以你可以把它看作是一個在 JavaScript 中的特殊領域,以同步化(Synchronous)會阻塞(Block)的概念來進行邏輯設計的存在。
錯誤處理
JavaScript 一直以來,最讓人詬病的就是非同步機制的錯誤處理很麻煩,也很囉唆。在使用 Generator 之後,我們可以直接用 try-catch 的方法進行錯誤處理。如下所示:try { yield function(done) { setTimeout(function() { // 拋出錯誤 done(new Error('WRONG')); }, 1000); } } catch(err) { // 處理錯誤 console.log(err); }
Koa 的設計上,只要將錯誤代入到 callback 的第一個參數,使第一個參數不是 null,就會拋出錯誤,讓 yield 外的 try-catch 去攔截,如此就可以很容易來處理非同步機制的錯誤。
從 yield 得到回傳結果
如果已經瞭解了 yield 的使用方式和邏輯,我們也可以等待非同步工作將一些結果回傳,就像下面的使用方式:var result = yield function(done) { ... }
在 Koa 中,確實作法只需要代入想要回傳的資料到 callback 的第二個參數即可:
var result = yield function(done) { setTimeout(function() { // 拋出錯誤 done(null, 'hello'); }, 1000); } console.log(result);
包裝成更好用的函數
我們固然可以直接使用匿名函數,讓 Generator 去執行,但這可能不是一個好的做法,多數情況,我們還是會將功能包裝成 API 函數的形式,增加重複利用的機會,如下:yield delay(3000);
如果你很熟悉 JavaScript 的開發,應該已經知道怎麼做,邏輯如前面所提及的範例一樣,只是額外進行一層包裝罷了:
function delay(interval) { return function(done) { // 照 interval 變數所給的數值,決定暫停時間長度 setTimeout(done, interval); }; }
後記
Generator 是一個會讓傳統 JavaScript 開發者抓狂的東西,不過 Koa 對其已經做了初步的包裝,只要熟悉 callback 的使用,都不會有太大問題。但這也代表 Koa 中所用到的 Generator 機制,和原始的 Generator 使用方法會有些差別,最大差別就是沒有 callback 的設計,得自己實作。所以,如果你是一個 Generator 的初學者,建議先不要直接學習原始 Generator 的使用方法,可以藉由 Koa 先熟悉並瞭解 Generator 的邏輯。等到都熟悉了 Generator 的概念後,再開始學習怎麼自行實作如同 Koa 的 callback。
留言
張貼留言