co 源码解析

co是一个使Generator自动执行的函数库,设计的非常精妙。

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

koa的中间件实现就是依赖了co,使处理异步代码写的像同步代码一样,摆脱了回调地狱
博客地址

co文件非常小,加上注释就240行,核心代码就几十行,其他都是一些辅助函数,比如判段类型和和将array object等转化成promise 这里我将这都算在toPromise函数内。

所以不算这些函数的话,实际上用上函数的就这只有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函数执行的gen的代码分析一下co函数的流程

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函数传入一个Generator类型的gen并返回Promiseyield 后面的表达式或者说异步操作都执行结束后,提供一个钩子处理函数的返回值。

Generator自执行通过两个函数onFulfillednext配合实现,首先一开始会执行一次onFulfilled()使gen函数开始执行

onFulfilled源码如下

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

作用主要是为了捕获异常和执行gen.next,如果代码下一次next操作没有问题,则交给next函数处理,反之将异常作为reject抛出,在这里 第一次执行后会在var a = yield work这里暂停, ret = gen.next(res)执行后 ret会得到的将会是

{ value: [Function: work], done: false } //并作为next参数执行next(ret)

next主要判定Generator是否已经执行结束,如果结束返回,反之判断还未结束将对yield后面的表达式转成promise然后继续执行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转化成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;
}

代码如上,通过类型判断执行具体转化函数, 具体可以去看源码, 另外在next函数也写有目前支持在yield的表达式类型为 a function, promise, generator, array, or object

回到next函数在这里将work 包装成Promise并在Promise执行成功后执行onFulfilled

在这里work的类型是generator所以co里实际上执行了co(work)

我们测试代码第二次执行onFulfilled与第一次不同的是,第二次会带有Promise返回的值执行,而这个值实际上就是work生成器执行结束后return的值在这里就是'yay'

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

如此重复到第四次,因为var c = yield work;执行完后已经没有下一个yield,所以第四次执行gen.next函数返回的ret.done === true并将return a + b + c;作为Promiseresolve返回。

所以大概流程如下

  1. 开始执行gen函数, 遇到yield暂停,异步处理yield后面的表达式
  2. 表达式执行结束后,返回执行结果(resolve),继续开始执行下一步操作
  3. 继续1的操作,直到函数结束

end

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 211,884评论 6 492
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 90,347评论 3 385
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 157,435评论 0 348
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 56,509评论 1 284
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 65,611评论 6 386
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 49,837评论 1 290
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 38,987评论 3 408
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 37,730评论 0 267
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 44,194评论 1 303
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 36,525评论 2 327
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 38,664评论 1 340
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 34,334评论 4 330
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 39,944评论 3 313
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 30,764评论 0 21
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,997评论 1 266
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 46,389评论 2 360
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 43,554评论 2 349

推荐阅读更多精彩内容

  • 异步编程对JavaScript语言太重要。Javascript语言的执行环境是“单线程”的,如果没有异步编程,根本...
    呼呼哥阅读 7,301评论 5 22
  • 简介 基本概念 Generator函数是ES6提供的一种异步编程解决方案,语法行为与传统函数完全不同。本章详细介绍...
    呼呼哥阅读 1,070评论 0 4
  • 弄懂js异步 讲异步之前,我们必须掌握一个基础知识-event-loop。 我们知道JavaScript的一大特点...
    DCbryant阅读 2,707评论 0 5
  • 本文作者就是我,简书的microkof。如果您觉得本文对您的工作有意义,产生了不可估量的价值,那么请您不吝打赏我,...
    microkof阅读 23,724评论 16 78
  • title标题: A Web Crawler With asyncio Coroutinesauthor作者: A...
    彰乐乐乐乐阅读 2,041评论 0 8