co源碼解讀


背景:

    閑來無事,翻了下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項目,所以會接觸到這些東西,就想寫點博客記錄一下;

本人文筆簡直負分,望各位海涵,如有什么不懂的,歡迎郵件騷擾。

jiashunming@outlook.com 

文章相關代碼會在GitHub更新:

https://github.com/Jiasm/blog-resource/tree/master/co 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM