co 源碼解析

co是一個使Generator自動執(zhí)行的函數(shù)庫,設(shè)計的非常精妙佳吞。

如果不知道Generator是什么,請看阮一峰的ECMAScript 6入門

koa的中間件實現(xiàn)就是依賴了co,使處理異步代碼寫的像同步代碼一樣,擺脫了回調(diào)地獄
博客地址

co文件非常小,加上注釋就240行,核心代碼就幾十行,其他都是一些輔助函數(shù),比如判段類型和和將array object等轉(zhuǎn)化成promise 這里我將這都算在toPromise函數(shù)內(nèi)舰褪。

所以不算這些函數(shù)的話,實際上用上函數(shù)的就這只有co, onFulFilled, next, toPromise

核心代碼如下

function co(gen) {
  var ctx = this;
  var args = slice.call(arguments, 1);

  return new Promise(function(resolve, reject) {
    if (typeof gen === 'function') gen = gen.apply(ctx, args);
    if (!gen || typeof gen.next !== 'function') return resolve(gen);

    onFulfilled();

    function onFulfilled(res) {
      var ret;
      try {
        ret = gen.next(res);
      } catch (e) {
        return reject(e);
      }
      next(ret);
      return null;
    }
    
    function onRejected(err) {
      var ret;
      try {
        ret = gen.throw(err);
      } catch (e) {
        return reject(e);
      }
      next(ret);
    }

    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) + '"'));
    }
  });
}

這里通過co函數(shù)執(zhí)行的gen的代碼分析一下co函數(shù)的流程

var co = require('./');
function sleep(ms) {
  return function(done){
    setTimeout(done, ms);
  }
}

function *work() {
  yield sleep(50);
  return 'yay';
}

function* gen() {
  var a = yield work;
  var b = yield work;
  var c = yield work;
  return a + b + c;
}

co(gen).then((data) => console.log(data))

這里的代碼選自co的測試用例

co函數(shù)傳入一個Generator類型的gen并返回Promiseyield 后面的表達(dá)式或者說異步操作都執(zhí)行結(jié)束后,提供一個鉤子處理函數(shù)的返回值吓歇。

Generator自執(zhí)行通過兩個函數(shù)onFulfillednext配合實現(xiàn),首先一開始會執(zhí)行一次onFulfilled()使gen函數(shù)開始執(zhí)行

onFulfilled源碼如下

 function onFulfilled(res) {
  var ret;
  try {
     ret = gen.next(res);
  } catch (e) {
     return reject(e);
  }
  console.log(ret);
  next(ret);
  return null;
 }

作用主要是為了捕獲異常和執(zhí)行gen.next,如果代碼下一次next操作沒有問題,則交給next函數(shù)處理,反之將異常作為reject拋出,在這里 第一次執(zhí)行后會在var a = yield work這里暫停, ret = gen.next(res)執(zhí)行后 ret會得到的將會是

{ value: [Function: work], done: false } //并作為next參數(shù)執(zhí)行next(ret)

next主要判定Generator是否已經(jīng)執(zhí)行結(jié)束,如果結(jié)束返回,反之判斷還未結(jié)束將對yield后面的表達(dá)式轉(zhuǎn)成promise然后繼續(xù)執(zhí)行onFulfilled,

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.done === false所以將對work轉(zhuǎn)化成promise

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;
}

代碼如上,通過類型判斷執(zhí)行具體轉(zhuǎn)化函數(shù), 具體可以去看源碼, 另外在next函數(shù)也寫有目前支持在yield的表達(dá)式類型為 a function, promise, generator, array, or object

回到next函數(shù)在這里將work 包裝成Promise并在Promise執(zhí)行成功后執(zhí)行onFulfilled

在這里work的類型是generator所以co里實際上執(zhí)行了co(work)

我們測試代碼第二次執(zhí)行onFulfilled與第一次不同的是,第二次會帶有Promise返回的值執(zhí)行,而這個值實際上就是work生成器執(zhí)行結(jié)束后return的值在這里就是'yay'

ret = gen.next(res);  // 第二次執(zhí)行`onFulfilled`后gen.next(res)中的res 就等于'yay';
var a = yield work; //所以在gen.next(res)執(zhí)行后 a = 'yay'
var b = yield work; //開始執(zhí)行 var b = yield work;

如此重復(fù)到第四次,因為var c = yield work;執(zhí)行完后已經(jīng)沒有下一個yield,所以第四次執(zhí)行gen.next函數(shù)返回的ret.done === true并將return a + b + c;作為Promiseresolve返回慰安。

所以大概流程如下

  1. 開始執(zhí)行gen函數(shù), 遇到yield暫停,異步處理yield后面的表達(dá)式
  2. 表達(dá)式執(zhí)行結(jié)束后,返回執(zhí)行結(jié)果(resolve),繼續(xù)開始執(zhí)行下一步操作
  3. 繼續(xù)1的操作,直到函數(shù)結(jié)束

end

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末腋么,一起剝皮案震驚了整個濱河市顽耳,隨后出現(xiàn)的幾起案子票摇,更是在濱河造成了極大的恐慌拘鞋,老刑警劉巖,帶你破解...
    沈念sama閱讀 206,378評論 6 481
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件矢门,死亡現(xiàn)場離奇詭異盆色,居然都是意外死亡,警方通過查閱死者的電腦和手機(jī)祟剔,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 88,356評論 2 382
  • 文/潘曉璐 我一進(jìn)店門隔躲,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人物延,你說我怎么就攤上這事宣旱。” “怎么了叛薯?”我有些...
    開封第一講書人閱讀 152,702評論 0 342
  • 文/不壞的土叔 我叫張陵浑吟,是天一觀的道長。 經(jīng)常有香客問我耗溜,道長组力,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 55,259評論 1 279
  • 正文 為了忘掉前任强霎,我火速辦了婚禮忿项,結(jié)果婚禮上蓉冈,老公的妹妹穿的比我還像新娘城舞。我一直安慰自己,他們只是感情好寞酿,可當(dāng)我...
    茶點故事閱讀 64,263評論 5 371
  • 文/花漫 我一把揭開白布家夺。 她就那樣靜靜地躺著,像睡著了一般伐弹。 火紅的嫁衣襯著肌膚如雪拉馋。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 49,036評論 1 285
  • 那天惨好,我揣著相機(jī)與錄音煌茴,去河邊找鬼。 笑死日川,一個胖子當(dāng)著我的面吹牛蔓腐,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播龄句,決...
    沈念sama閱讀 38,349評論 3 400
  • 文/蒼蘭香墨 我猛地睜開眼回论,長吁一口氣:“原來是場噩夢啊……” “哼散罕!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起傀蓉,我...
    開封第一講書人閱讀 36,979評論 0 259
  • 序言:老撾萬榮一對情侶失蹤欧漱,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后葬燎,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體误甚,經(jīng)...
    沈念sama閱讀 43,469評論 1 300
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 35,938評論 2 323
  • 正文 我和宋清朗相戀三年谱净,在試婚紗的時候發(fā)現(xiàn)自己被綠了靶草。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 38,059評論 1 333
  • 序言:一個原本活蹦亂跳的男人離奇死亡岳遥,死狀恐怖奕翔,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情浩蓉,我是刑警寧澤派继,帶...
    沈念sama閱讀 33,703評論 4 323
  • 正文 年R本政府宣布,位于F島的核電站捻艳,受9級特大地震影響驾窟,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜认轨,卻給世界環(huán)境...
    茶點故事閱讀 39,257評論 3 307
  • 文/蒙蒙 一绅络、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧嘁字,春花似錦恩急、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,262評論 0 19
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至纯续,卻和暖如春随珠,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背猬错。 一陣腳步聲響...
    開封第一講書人閱讀 31,485評論 1 262
  • 我被黑心中介騙來泰國打工窗看, 沒想到剛下飛機(jī)就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人倦炒。 一個月前我還...
    沈念sama閱讀 45,501評論 2 354
  • 正文 我出身青樓显沈,卻偏偏與公主長得像,于是被迫代替她去往敵國和親析校。 傳聞我的和親對象是個殘疾皇子构罗,可洞房花燭夜當(dāng)晚...
    茶點故事閱讀 42,792評論 2 345

推薦閱讀更多精彩內(nèi)容

  • 異步編程對JavaScript語言太重要铜涉。Javascript語言的執(zhí)行環(huán)境是“單線程”的,如果沒有異步編程遂唧,根本...
    呼呼哥閱讀 7,298評論 5 22
  • 簡介 基本概念 Generator函數(shù)是ES6提供的一種異步編程解決方案芙代,語法行為與傳統(tǒng)函數(shù)完全不同。本章詳細(xì)介紹...
    呼呼哥閱讀 1,068評論 0 4
  • 弄懂js異步 講異步之前盖彭,我們必須掌握一個基礎(chǔ)知識-event-loop纹烹。 我們知道JavaScript的一大特點...
    DCbryant閱讀 2,697評論 0 5
  • 本文作者就是我,簡書的microkof召边。如果您覺得本文對您的工作有意義铺呵,產(chǎn)生了不可估量的價值,那么請您不吝打賞我隧熙,...
    microkof閱讀 23,720評論 16 78
  • title標(biāo)題: A Web Crawler With asyncio Coroutinesauthor作者: A...
    彰樂樂樂樂閱讀 2,036評論 0 8