亚洲免费在线-亚洲免费在线播放-亚洲免费在线观看-亚洲免费在线观看视频-亚洲免费在线看-亚洲免费在线视频

異步編程之co——源碼分析

系統(tǒng) 2051 0

異步編程系列教程:

  1. (翻譯)異步編程之Promise(1)——初見魅力
  2. 異步編程之Promise(2):探究原理
  3. 異步編程之Promise(3):拓展進(jìn)階
  4. 異步編程之Generator(1)——領(lǐng)略魅力
  5. 異步編程之Generator(2)——剖析特性
  6. 異步編程之co——源碼分析

如何使用co


大家如果能消化掉前面的知識,相信這一章的分析也肯定是輕輕松松的。我們這一章就來說說,我們之前一直高調(diào)提到的 co 庫。 co 庫,它用Generator和Promise相結(jié)合,完美提升了我們異步編程的體驗(yàn)。我們首先看看如何使用 co 的,我們?nèi)耘f以之前的讀取Json文件的例子看看:

    
      // 注意readFile已經(jīng)是Promise化的異步API 
co(function* (){
    var filename = yield readFile('hello3.txt', 'utf-8');
    var json = yield readFile(filename, 'utf-8');
    return JSON.parse(json).message;
}).then(console.log, console.error);
    
  

大家看上面的代碼,甚至是可以使用同步的思維,不用去理會回調(diào)什么鬼的。我們 readFile() 得到 filename ,然后再次 readFile() 得到 json ,解析完json后輸出就結(jié)束了,非常清爽。大家如果不相信的話,可以使用原生的異步api嘗試一下, fs.readFile() 像上面相互有依賴的,絕對惡心!

我們可以看到,僅僅是在promise化的異步api前有個(gè) yield 標(biāo)識符,就可以使 co 完美運(yùn)作。上一篇我們也假想過 co 的內(nèi)部是如何實(shí)現(xiàn)的,我們再理(fu)順(zhi)一次:

  1. 我們調(diào)用遍歷器的 next() 得到該異步的promise對象
  2. 在promise對象的 then() 中的 resolve 對數(shù)據(jù)進(jìn)行處理
  3. 把處理后的數(shù)據(jù)作為參數(shù) res 傳入 next(res) ,繼續(xù)到下一次異步操作
  4. 重復(fù)2,3步驟。直到迭代器的 done: true ,結(jié)束遍歷。

如果不清楚我們上面說過的Generator遍歷器或promise對象的,可以先放一放這篇文章,從之前的幾篇看起。

進(jìn)入co的世界


獲得遍歷器

co的源碼包括注釋和空行僅僅才240行,不能再精簡!我們抽出其中主要的代碼來進(jìn)行分析。

    
      function co(gen) {
  var ctx = this; // context
  
  // return a promise
  return new Promise(function(resolve, reject) {
    if (typeof gen === 'function') gen = gen.call(ctx); // 調(diào)用構(gòu)造器來獲得遍歷器
    if (!gen || typeof gen.next !== 'function') return resolve(gen);
    
    //...下面代碼暫時(shí)省略...
   })
}
    
  

這里我們需要關(guān)注的有兩點(diǎn):

  1. co函數(shù)最終返回的是一個(gè)Promise。
  2. 第6行代碼,我們可以看到gen變量一開始就已經(jīng)自身調(diào)用了。也就是gen從構(gòu)造器變成了遍歷器。

    遍歷器開始遍歷
    ---
    我們首先看看 co 內(nèi)部的 next(ret) 函數(shù),它是整個(gè)遍歷器自動運(yùn)行的關(guān)鍵。
    
          function next(ret) {
      if (ret.done) return resolve(ret.value);
      var value = toPromise.call(ctx, ret.value);
      if (value && isPromise(value)) return value.then(onFulfilled, onRejected);
      return onRejected(new TypeError('You may only yield a function, promise, generator, array, or object, '
        + 'but the following object was passed: "' + String(ret.value) + '"'));
    }
    
  

我們可以看到,ret參數(shù)有 done value ,那么ret肯定就是遍歷器每次 next() 的結(jié)果。如果發(fā)現(xiàn)遍歷器遍歷結(jié)束的話,便直接return整個(gè)大Promise的 resolve(ret.value) 方法結(jié)束遍歷。對了,此遍歷器的 next() 和co的 next() 在這里是不一樣的。當(dāng)然你可以認(rèn)為co將遍歷器的 next() 又封裝了一遍方便源碼使用。

接著看,如果并沒有完成遍歷。我們就會對 ret.value 調(diào)用 toPromise() ,這里有知識點(diǎn)延伸,暫且先跳過,因?yàn)槲覀? 一個(gè) promise化的異步操作就是返回promise的。不知道大家get到point沒?我就透漏一點(diǎn),當(dāng)是數(shù)組或?qū)ο髸r(shí), co 會識別并支持多異步的并行操作,先不管~~

我們在保證我們調(diào)用異步操作得到的 value 是promise后,我們就會調(diào)用 value.then() 方法為promise的 onFulfilled() onRejected() 進(jìn)行回調(diào)的綁定。也就是說,這段時(shí)間程序都是在干其他和遍歷器無關(guān)的事的。遍歷器沒有得到遍歷器的 next() 指令,就一直靜靜的等著。我們可以想到, next() 指令,必定是放在了那兩個(gè)回調(diào)函數(shù)( onFulfilled onRejected )里。

自動運(yùn)行

promise化的異步API是先綁定了回調(diào)方法,然后等待異步完成后進(jìn)行觸發(fā)。所以我們把遍歷器繼續(xù)遍歷的 next() 指令放在回調(diào)中,就可以達(dá)到回調(diào)返回?cái)?shù)據(jù)后再調(diào)用遍歷器 next() 指令,遍歷器才會繼續(xù)下一個(gè)異步操作。

    
      	function onFulfilled(res) {
      var ret;
      try {
        ret = gen.next(res); // 遍歷器進(jìn)行遍歷,ret是此次遍歷項(xiàng)
      } catch (e) {
        return reject(e);
      }
      next(ret); // ret.value is a promise
    }
    
  

我們看到第四行,通過調(diào)用遍歷器的 next(res) ,再次啟動遍歷器得到新的遍歷結(jié)果,再傳入 co next() 里,重復(fù)之前的操作,達(dá)到自動運(yùn)行的效果。這里需要注意一個(gè)地方,我們是通過向遍歷器的 next(res) 傳入 res 變量來實(shí)現(xiàn)將異步執(zhí)行后的數(shù)據(jù)保存到遍歷器里。

理解的關(guān)鍵

我相信我不可能說的很明白,讓大家一下子就知道關(guān)鍵重點(diǎn)是哪個(gè)。我自己也是悟了不少時(shí)間的,最終發(fā)現(xiàn)那個(gè)可以使思路清晰的就是 Deferred 延遲對象。我在第二篇也有著重說過 Deferred 延遲對象,它最重要的一點(diǎn)就是,它是用來延遲觸發(fā)回調(diào)的。我們先通過延遲對象的promise進(jìn)行回調(diào)的綁定,然后在Node的異步操作的回調(diào)中觸發(fā)promise綁定的函數(shù),實(shí)現(xiàn)異步操作。當(dāng)然這里也是如此,我們是把遍歷器的 next() 指令延遲到回調(diào)時(shí)再觸發(fā)。當(dāng)然在 co 源碼里是直接使用了ES6的promise原生對象,我們看不到 deferred 的存在。

所以我很早前就說了,promise對理解 co 至關(guān)重要。之前在promise上也花費(fèi)了特別大的精力去理解,并分析原理。所以大家如果沒有看之前的有關(guān)promise文章的,最好都回去看一看,絕對有好處!

co其他的內(nèi)容


分析完 co 最關(guān)鍵的部分,接下來就是其他各種有用的源碼分析。關(guān)于 thunk 轉(zhuǎn)化為 promise 我就不說了,畢竟它也是被淘汰了的東西。那要說的東西其實(shí)就兩個(gè),一個(gè)是多異步并行,一個(gè)是將 co-generator 轉(zhuǎn)化為常規(guī)函數(shù)。我們一個(gè)一個(gè)來講:

多異步并行

之前也有提到過,就是我們需要對迭代對象的值進(jìn)行 toPromise() 操作。這個(gè)操作顧名思義,就是將所有需要yield的值,通通轉(zhuǎn)化為promise對象。它的源碼就是這樣的,并不能看到實(shí)質(zhì)的東西:

    
      function toPromise(obj) {
  if (!obj) return obj;
  if (isPromise(obj)) return obj;
  if (isGeneratorFunction(obj) || isGenerator(obj)) return co.call(this, obj);
  if ('function' == typeof obj) return thunkToPromise.call(this, obj);
  if (Array.isArray(obj)) return arrayToPromise.call(this, obj);
  if (isObject(obj)) return objectToPromise.call(this, obj);
  return obj;
}
    
  

我們還記得在 co next() 函數(shù)里可以看到有一個(gè)注釋是這樣的:

'You may only yield a function, promise, generator, array, or object'

意思是,我們不僅僅只可以yield一個(gè)promise對象。function和promise我們就不說了,重點(diǎn)就是在array和object上,它們都是通過遞歸調(diào)用 toPromise() 來實(shí)現(xiàn)每一個(gè)并行操作都是promise化的。

數(shù)組Array

我們先看看相對簡單的array的源碼:

    
      function arrayToPromise(obj) {
  return Promise.all(obj.map(toPromise, this));
}
    
  

map是ES5的array的方法,這個(gè)相信也有人經(jīng)常使用的。我們將數(shù)組里的每一項(xiàng)的值,再進(jìn)行一次 toPromise 操作,然后得到全部都是promise對象的數(shù)組交給 Promise.all 方法使用。這個(gè)方法在promise文章的第二篇也講過它的實(shí)現(xiàn),它會在所有異步都執(zhí)行完后才會執(zhí)行回調(diào)。最后 resolve(res) res 是一個(gè)存有所有異步操作執(zhí)行完后的值的數(shù)組。

對象Object

Object就相對復(fù)雜些,不過原理依然是大同小異的,最后都是回歸到一個(gè)promise數(shù)組然后使用 Promise.all() 。使用Object的好處就是,異步操作的名字和值是可以對應(yīng)起來的,來看看代碼:

    
      function objectToPromise(obj){
  var results = new obj.constructor();
  var keys = Object.keys(obj); // 得到的是一個(gè)存對象keys名字的數(shù)組
  var promises = [];           // 用于存放promise
  for (var i = 0; i < keys.length; i++) {
    var key = keys[i];
    var promise = toPromise.call(this, obj[key]);
    if (promise && isPromise(promise)) defer(promise, key);
    else results[key] = obj[key];
  }
  return Promise.all(promises).then(function () {
    return results;
  });

  function defer(promise, key) {
    // predefine the key in the result
    results[key] = undefined;
    promises.push(promise.then(function (res) {
      results[key] = res;
    }));
  }
}
    
  

第一個(gè)就是新建一個(gè)和傳入的對象一樣構(gòu)造器的對象( 這個(gè)寫法太厲害了 )。我們先獲得了對象的所有的keys屬性名,然后根據(jù)keys,來獲取到每一個(gè)對象的屬性值。一樣是用 toPromise() 讓屬性值——也就是并行操作promise化,當(dāng)然非promise的值就會直接存到results這個(gè)對象里。如果是promise,就會執(zhí)行內(nèi)部定義的 defer(promise, key) 函數(shù)。

所以理解defer函數(shù)是關(guān)鍵,我們看到是在defer函數(shù)里,我們才將當(dāng)前的promise推入到promises數(shù)組里。并且每一個(gè)promise都是綁定了一個(gè) resolve() 方法的,就是將結(jié)果保存到 results 的對象中。最后我們就得到一組都是promise的數(shù)組,通過 Promise.all() 方法進(jìn)行異步并行操作,這樣每個(gè)promise的結(jié)果都會保存到result對象相應(yīng)的key里。而我們需要進(jìn)行數(shù)據(jù)操作的也就是那個(gè)對象里的數(shù)據(jù)。

這里強(qiáng)烈建議大家動手模擬實(shí)現(xiàn)一遍 objectToPromise。

co.wrap(*generatorFunc)
---
下一個(gè)很有用的東西就是 co.wrap() ,它允許我們將 co-generator 函數(shù)轉(zhuǎn)化成常規(guī)函數(shù),我覺得這個(gè)還是需要舉例子來表明它的作用。假設(shè)我們有多個(gè)異步的讀取文件的操作,我們用co來實(shí)現(xiàn)。

    
      //讀取文件1
co(function* (){
    var filename = yield readFile('hello1.txt', 'utf-8');
    return filename;
}).then(console.log, console.error);
//讀取文件2
co(function* (){
    var filename = yield readFile('hello2.txt', 'utf-8');
    return filename;
}).then(console.log, console.error);
    
  

天啊,我仿佛又回到了不會使用函數(shù)的年代,一個(gè)功能一段函數(shù),不能復(fù)用。當(dāng)然 co.wrap() 就是幫你解決這個(gè)問題的。

    
      var getFile = co.wrap(function* (file){
    var filename = yield readFile(file, 'utf-8');
    return filename;
});

getFile('hello.txt').then(console.log);
getFile('hello2.txt').then(console.log);
    
  

例子很簡單,我們可以將 co-generator 里的變量抽取出來,形成一個(gè)常規(guī)的Promise函數(shù)(regular-function)。這樣子就無論是復(fù)用性還是代碼結(jié)構(gòu)都是優(yōu)化了不少。

既然知道了怎么用,就該看看它內(nèi)部如何實(shí)現(xiàn)的啦,畢竟這是一次源碼分析。其實(shí)如果對函數(shù)柯里化(偏函數(shù))比較了解,就會覺得非常簡單。

    
      co.wrap = function (fn) {
  createPromise.__generatorFunction__ = fn; // 這個(gè)應(yīng)該是像函數(shù)constructor的東西
  return createPromise;
  function createPromise() {
    return co.call(this, fn.apply(this, arguments));
  }
};
    
  

就是一個(gè)偏函數(shù),借助于高階函數(shù)的特性,返回一個(gè)新函數(shù) createPromise() ,然后傳給它的參數(shù)都會被導(dǎo)入到Generator函數(shù)中。

異步編程之co——源碼分析


更多文章、技術(shù)交流、商務(wù)合作、聯(lián)系博主

微信掃碼或搜索:z360901061

微信掃一掃加我為好友

QQ號聯(lián)系: 360901061

您的支持是博主寫作最大的動力,如果您喜歡我的文章,感覺我的文章對您有幫助,請用微信掃描下面二維碼支持博主2元、5元、10元、20元等您想捐的金額吧,狠狠點(diǎn)擊下面給點(diǎn)支持吧,站長非常感激您!手機(jī)微信長按不能支付解決辦法:請將微信支付二維碼保存到相冊,切換到微信,然后點(diǎn)擊微信右上角掃一掃功能,選擇支付二維碼完成支付。

【本文對您有幫助就好】

您的支持是博主寫作最大的動力,如果您喜歡我的文章,感覺我的文章對您有幫助,請用微信掃描上面二維碼支持博主2元、5元、10元、自定義金額等您想捐的金額吧,站長會非常 感謝您的哦!!!

發(fā)表我的評論
最新評論 總共0條評論
主站蜘蛛池模板: 精品久久久久久综合日本 | 久久亚洲精品中文字幕三区 | 99视频热| 欧美日产 | 色狠狠色综合久久8狠狠色 色狠狠婷婷97 | 大美女久久久久久j久久 | 手机看片欧美 | 香蕉免费一级视频在线观看 | 国产在线精品福利91香蕉 | 大学生一级毛片高清版 | 播放一级录像片 | 免费aa毛片| 亚洲国产日韩欧美mv | 深夜免费看片 | 亚洲国产一区二区a毛片 | 一及 片日本 | 在线播放 亚洲 | 一级毛片aa| 国产香蕉75在线播放 | 亚洲a在线视频 | 亚洲欧美另类图片 | 九九九九热精品视频 | 青青青在线视频人视频在线 | 九九99久麻豆精品视传媒 | 深夜你懂的在线网址入口 | 久久成人精品免费播放 | 亚洲黄色小视频 | 亚洲在线视频免费观看 | 奇米777视频二区中文字幕 | 偷拍清纯高清视频在线 | 狠狠色狠狠色 | 亚洲最大在线视频 | 欧美综合一区二区三区 | 亚洲四虎| 国产精品自拍视频 | 亚洲一级毛片在线观播放 | 色姑娘综合 | 四虎欧美 | 久久免费在线观看 | 欧美日韩精品一区二区三区四区 | 四虎免费播放观看在线视频 |