co-实现原理分析

generator函数可以理解成一个异步操作的容器,它装着一些异步操作,但并不会在实例化以后立即执行。而co的思想是在恰当的时候执行这些异步操作。那么就需要一种机制,在一个异步操作执行完毕以后通知下一个异步操作开始执行。额,这句话听起来就有点耳熟了。这不就是回调函数或者promise干的事么。确实,co要求generator里yield的是thunk或者promise就是这个道理。thunk就是一种回调机制。
那么co就有两种实现方式,promise或者thunk。co4.0之前是用thunk实现的,之后是用promise实现的。

以下所有代码可以在github上查看源码。

基于thunk实现co

查看thunk函数。
大致思路就是,在yield中调用thunk,thunk回调中调用genetator的next方法,直至next返回的对象{value, done}中done为true。

function run(fn) {
//实例化generator,但因为generator的特性,并没有调用
  var gen = fn();

  function next(err, data) {
    var result = gen.next(data);
    //在调用下一次next之前,你可以加一些自己的操作
    console.log('--------next in thunk----------');
    if (result.done) return;
    //{value:next传入的值,done:boolean},由于fn应为一个yield thunk的generator,故value接受的其实是一个回调,这个回调在co(或者说本函数run中就是generator的next)
    result.value(next);
  }

  next();
}

调用

var readFile = thunkify(fs.readFile);

var gen = function* (){
  var r1 = yield readFile('./hi.txt');
  console.log(r1.toString());
  var r2 = yield readFile('./hello.txt');
  console.log(r2.toString());
};

console.log('------------------run thunk-----------------');
run(gen);

执行结果:
这里写图片描述
thunkify是如何实现的请移步github查看,你也可以使用require的方式,nodejs有这个工具。

基于promise实现co

大致思路同thunk一样,只是把回调的实现方式变成了promise,同理就需要genenrator中yield的是promise。

function run2(gen){
  var g = gen();

  function next(data){
    var result = g.next(data);
    if (result.done) return result.value;
    result.value.then(function(data){
      next(data);
    });
  }

  next();
} 

调用方式:

var readFile2 = function (fileName){
  return new Promise(function (resolve, reject){
    fs.readFile(fileName, function(error, data){
      if (error) reject(error);
      resolve(data);
    });
  });
};

var gen2 = function* (){
  var f1 = yield readFile2('./hi.txt');
  console.log(f1.toString());
  var f2 = yield readFile2('./hello.txt');  
  console.log(f2.toString());
};

console.log('-----------------run2 promise-----------------');
run2(gen2);

执行结果:
这里写图片描述

co源码分析

基本原理就是以上所说。co就是对以上两种方式进行扩展、封装。

function co(gen) {
  var ctx = this;

  //如果是generatorFunction,就执行 获得对应的generator对象
  if (typeof gen === 'function') gen = gen.call(this);

  //返回一个promise
  return new Promise(function(resolve, reject) {

    //初始化入口函数,第一次调用
    onFulfilled();

    //成功状态下的回调
    function onFulfilled(res) {
      var ret;
      try {
        //拿到第一个yield返回的对象值ret
        ret = gen.next(res);
      } catch (e) {
        //出错直接调用reject把promise置为失败状态
        return reject(e);
      }
      //开启调用链
      next(ret);
    }

    function onRejected(err) {
      var ret;
      try {
        //抛出错误,这边使用generator对象throw。这个的好处是可以在co的generatorFunction里面使用try捕获到这个异常。
        ret = gen.throw(err);
      } catch (e) {
        return reject(e);
      }
      next(ret);
    }


    function next(ret) {
      //如果执行完成,直接调用resolve把promise置为成功状态
      if (ret.done) return resolve(ret.value);
      //把yield的值转换成promise
      //支持 promise,generator,generatorFunction,array,object
      //toPromise的实现可以先不管,只要知道是转换成promise就行了
      var value = toPromise.call(ctx, ret.value);

      //成功转换就可以直接给新的promise添加onFulfilled, onRejected。当新的promise状态变成结束态(成功或失败)。就会调用对应的回调。整个next链路就执行下去了。
      if (value && isPromise(value)) return value.then(onFulfilled, onRejected);

      //否则说明有错误,调用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) + '"'));
    }
  });
}

function isPromise(obj) {
  return 'function' == typeof obj.then;
}

在co4.0中,你依然可以yield thunk,因为co会将其转换为promise,你可以yield object或者array,co也会将其转换为promise,且object或array的成员会同步开始执行。

你可能感兴趣的:(nodejs,co)