背景:
閑來無事,翻了下co的源碼來看,源碼短小精悍,算上注釋,一共240行左右;
決定寫一篇博客來記錄下學習的心得。
TJ大神的co:https://github.com/tj/co
作用:
co通過將Generator函數拆成一個Promise將碼農從callback hell中拯救了出來;
下邊放出一段代碼,對比下co與普通回調版本的區別:
1 /** 2 * 回調版本 3 */ 4 5 let fs = require('fs') 6 7 fs.readFile('./package.json', (err, data) => { 8 if (err) { 9 return console.log(err) 10 } 11 console.log(data.toString()) 12 fs.readFile('./package.json', (err, data) => { 13 if (err) { 14 return console.log(err) 15 } 16 console.log(data.toString()) 17 }) 18 }) 19 20 /** 21 * co版本 22 */ 23 24 let co = require('co') 25 let fs = require('fs') 26 27 co(function * () { 28 let a = yield fs.readFile.bind(null, './package.json') 29 console.log(a.toString()) 30 let b = yield fs.readFile.bind(null, './package.json') 31 console.log(b.toString()) 32 }).then(console.log, console.error)
從代碼上看,貌似co是一個同步執行的過程呢。當然,也只是看起來像而已。
正題:
先來說一下co整個執行的過程:
- 調用co,傳入一個Generator函數,函數會返回一個Promise對象
- 如果傳入參數為Generator函數,會執行該函數來進行Generator的初始化
- 手動執行一次next() 這時Generator函數就會停在第一次遇到yield關鍵字的地方
- 獲取到yield后邊的值,將其轉換為一個Promise函數,然后執行之
- 重復上邊兩步,直到函數執行完畢
co關於yield后邊的值也是有一定的要求的,只能是一個 Function|Promise|Generator | Array | Object;
而 Array和Object中的item也必須是 Function|Promise|Generator。
並且關於function 普通函數並不一定會得到預期的結果,co需要的是 接收一個回調函數 並執行的函數,類似於這樣:
1 function doSomething (callback) { 2 callback(null, 'hello') 3 } 4 co(function * () { 5 let result = yield doSomething 6 console.log(result) // => hello 7 })
總而言之,co執行的肯定是一個Promise,而co會幫你把其他幾種類型的值轉換為Promise,co絕大部份的代碼都是在處理類型的轉換;
當然,在講類型轉換的那一塊之前,還是將co執行Generator的那幾個函數說一下子,也就是調用co返回的Promise中的那三個函數(onFulfilled、onRejected、next);
因next與Generator對象的next方法名相同 這里使用 gen.next 表示 Generator對象的next方法。
onFulfilled:
調用gen.next並將上次執行的結果傳入gen.next;
調用next,將gen.next返回的值傳入next。
onRejected:
執行流程與 onFulfilled 一致,只不過是將調用的 gen.next 換為了 gen.throw 用來將錯誤異常拋出。
next:
函數會判斷傳入參數的done屬性,如果為true( 則表示該Generator已經執行完畢),會調用co返回的Promise對象的resolve方法,結束代碼執行;
如果done為false 則表示還需要繼續執行,這里會將 yield后邊的值(參數的value屬性)轉換為Promise,並調用then方法傳入 onFulfilled 和 onRejected兩個函數。
co整個的執行流程其實就是這樣的-.-
剩余代碼所完成的事情就是將各種不同的類型轉換為可執行的Promise對象。
thunkToPromise(Function):
函數返回一個Promise對象,在Promise內部執行了傳入的function;
並會認為回調的第一個參數為Error(這個貌似是個標准...);
將其余參數打包到一個數組中返回。
arrayToPromise(Array):
Promise有一個方法叫做all,會返回數組中所有Promise執行后的返回值(如果有其中一項被reject掉,所有的都會被reject);
方法會返回 Promise.all() 的執行結果
1 Promise.all([ 2 Promise.resolve('hello'), 3 Promise.resolve('world') 4 ]).then(data => { 5 console.log(data) // => ['hello', 'world'] 6 })
objectToPromise(Object):
函數用來將一個Object對象轉換為Promise;
應該是co源碼中行數最多的一個函數了😜 具體做的事兒呢;
就是將一個Object的每一個key都轉換為Promise,並塞到一個數組中;
執行Promise.all()將上邊的數組塞進去;
當某一個key所對應的Promise函數執行完畢后,會將執行的結果塞回對應的key中;
全部執行完畢后,就會返回該Object。
1 { 2 a: Promise.resolve('hello'), 3 b: Promise.resolve('world') 4 } 5 6 // => 7 8 { 9 a: 'hello', 10 b: 'world' 11 }
其余的幾個函數就是判斷類型了, isPromise、isGenerator、isGeneratorFunction、isObject。
小記:
因我司在用koa來搭建web項目,所以會接觸到這些東西,就想寫點博客記錄一下;
本人文筆簡直負分,望各位海涵,如有什么不懂的,歡迎郵件騷擾。
文章相關代碼會在GitHub更新: