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

你可能感兴趣的:(co 源码解析)