co源码分析(thunk版本3.1.0)

Posted mengff

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了co源码分析(thunk版本3.1.0)相关的知识,希望对你有一定的参考价值。

co的thunk版本,就是将所有 函数,generator,generator function,object,array,promise,都转换为thunk函数,在thunk函数的回调中,切换外部包装的generator的状态,即调用next方法,来依次执行所有的异步任务。其中的,object和array,通过循环的方式,来并行执行thunk函数。

具体的源码注释如下:

//方便截取函数参数
var slice = Array.prototype.slice;

//导出co函数
module.exports = co;

//Wrap the given generator `fn` and return a thunk.
//包裹generator,并且返回一个thunk函数,执行这个thunk函数,就可以开始整个异步流程
//var thunk = co(function* (){...}); 
//开始执行 thunk();
function co(fn) {
  var isGenFun = isGeneratorFunction(fn);

  return function (done) {
    var ctx = this;

    // in toThunk() below we invoke co()
    // with a generator, so optimize for
    // this case
    var gen = fn;

    // we only need to parse the arguments
    // if gen is a generator function.
    if (isGenFun) {
      var args = slice.call(arguments), len = args.length;
      //判断最后一个参数是否函数
      var hasCallback = len && ‘function‘ == typeof args[len - 1];
      //末参是回调,done=回调,不是记录错误
      done = hasCallback ? args.pop() : error;
      //是一个generator function,就是执行它,得到一个generator
      gen = fn.apply(this, args);
    } 
    else {
      //不是generator,就为入参,或记录错误
      done = done || error;
    }
    
    //启动执行,next会递归调用,直至所有异步函数执行完毕
    next();

    // #92
    // wrap the callback in a setImmediate
    // so that any of its errors aren‘t caught by `co`
    function exit(err, res) {
      setImmediate(function(){
        done.call(ctx, err, res);
      });
    }

    function next(err, res) {
      var ret;

      // res相当于err剩余的所有参数
      if (arguments.length > 2) res = slice.call(arguments, 1);

      //有错误,generator抛出错误,退出
      if (err) {
        try {
          ret = gen.throw(err);
        } catch (e) {
          return exit(e);
        }
      }

      //无错误,gen执行next,执行一次异步函数,
      if (!err) {
        try {
          ret = gen.next(res);
        } catch (e) {
          return exit(e);
        }
      }

      // done为true,执行完所有异步函数,退出
      if (ret.done) return exit(null, ret.value);

      // 把ret.value得到的函数转换为thunk函数
      ret.value = toThunk(ret.value, ctx);

      // ret.value转换为thunk函数成功
      if (‘function‘ == typeof ret.value) {
        var called = false;
        try {
          ret.value.call(ctx, function(){
            //防止重复调用
            if (called) return;
            called = true;
            //递归调用next函数,继续执行下面异步函数,自执行核心部分
            //此function是ret.value这个thunk函数的回调函数
            //arguments是回调函数参数,传递给next,包含了err和res,作为gen的throw和next的参数
            next.apply(ctx, arguments);
          });
        } catch (e) {
          setImmediate(function(){
            if (called) return;
            called = true;
            next(e);
          });
        }
        return;
      }

      // ret.value转换为thunk函数失败,提示只能是以下类型
      next(new TypeError(‘You may only yield a function, promise, generator, array, or object, ‘
        + ‘but the following was passed: "‘ + String(ret.value) + ‘"‘));
    }
  }
}


//Convert `obj` into a normalized thunk.
//将任何类型转换为thunk
function toThunk(obj, ctx) {
  //是generator function,obj.call(ctx)返回generator,再用co调用
  if (isGeneratorFunction(obj)) {
    return co(obj.call(ctx));
  }
  //是generator,用co直接调用
  if (isGenerator(obj)) {
    return co(obj);
  }
  //是promise,转换为thunk函数
  if (isPromise(obj)) {
    return promiseToThunk(obj);
  }
  //是函数,直接返回本身
  if (‘function‘ == typeof obj) {
    return obj;
  }
  //是对象或者数组,转换为thunk函数
  if (isObject(obj) || Array.isArray(obj)) {
    return objectToThunk.call(ctx, obj);
  }
  
  //都不是,直接返回对象,未成功转换为thunk function,co的next函数会报错,提示格式不符合
  return obj;
}

// Convert an object of yieldables to a thunk.
// 将thunk数组,或thunk对象,转换为一个thunk函数
// 即将 arr = [thunk1,thunk2] 转换为 一个thunk
// 或将 obj = { key1: thunk1, key2: thunk2 } 转换为 一个thunk
function objectToThunk(obj){
  var ctx = this;
  var isArray = Array.isArray(obj);

  return function(done){
    //获取keys,对象是key,数组是索引
    var keys = Object.keys(obj);
    var pending = keys.length;
    //初始化results的为一个同长度数组,或同类型对象
    var results = isArray
      ? new Array(pending) // predefine the array length
      : new obj.constructor();
    var finished;

    //对象或数组长度为0,结束
    if (!pending) {
      setImmediate(function(){
        done(null, results)
      });
      return;
    }

    // prepopulate object keys to preserve key ordering
    // 对象类型,results按照obj的key,全部初始化为undefined
    if (!isArray) {
      for (var i = 0; i < pending; i++) {
        results[keys[i]] = undefined;
      }
    }
    
    //所有对象或数组key对应的函数传入run函数执行
    for (var i = 0; i < keys.length; i++) {
      run(obj[keys[i]], keys[i]);
    }

    function run(fn, key) {
      if (finished) return;
      try {
        //函数转化为thunk函数
        fn = toThunk(fn, ctx);
        
        //fn不是function,则转化为thunk函数失败
        if (‘function‘ != typeof fn) {
          //记录函数本身
          results[key] = fn;
          //pending数量减小,当pending为0时候,执行done,结束
          return --pending || done(null, results);
        }
        
        //fn是thunk函数,执行fn
        fn.call(ctx, function(err, res){
          if (finished) return;

          //报错,结束,传递错误
          if (err) {
            finished = true;
            return done(err);
          }
          
          //在results中,用对应的key记录fn执行结果,合法函数最终被记录
          results[key] = res;
          //减小待处理数量,为0,就结束
          --pending || done(null, results);
        });
      } catch (err) {
        //有任何报错,就结束
        finished = true;
        done(err);
      }
    }
  }
}

//promsie转thunk
function promiseToThunk(promise) {
  //返回只有一个回调参数fn的函数
  return function(fn){
    //回调fn遵循error first原则,在then的第一个回调中,error是null,data是res,传入fn(null,res)
    //在then的第二个回调中,fn直接用作error callback,只接收一个err参数
    //最终fn是形如 fn(err,data) 这种形式
    promise.then(function(res) {
      fn(null, res);
    }, fn);
  }
}

//判断是Promise
function isPromise(obj) {
  return obj && ‘function‘ == typeof obj.then;
}

//generator function的返回值,就是一个interator
function isGenerator(obj) {
  return obj && ‘function‘ == typeof obj.next && ‘function‘ == typeof obj.throw;
}

// 形如 function* (){...} 都是 generator function
function isGeneratorFunction(obj) {
  return obj && obj.constructor && ‘GeneratorFunction‘ == obj.constructor.name;
}

//判断是对象
function isObject(val) {
  return val && Object == val.constructor;
}

/**
 * Throw `err` in a new stack.
 *
 * This is used when co() is invoked
 * without supplying a callback, which
 * should only be for demonstrational
 * purposes.
 *
 * @param {Error} err
 * @api private
 */

function error(err) {
  if (!err) return;
  setImmediate(function(){
    throw err;
  });
}

 

以上是关于co源码分析(thunk版本3.1.0)的主要内容,如果未能解决你的问题,请参考以下文章

Redux异步解决方案之Redux-Thunk原理及源码解析

使用带有 promise 而不是 thunk 的 co 库有啥好处?

手写Redux-Saga源码

Android 插件化VirtualApp 源码分析 ( 目前的 API 现状 | 安装应用源码分析 | 安装按钮执行的操作 | 返回到 HomeActivity 执行的操作 )(代码片段

Android 逆向整体加固脱壳 ( DEX 优化流程分析 | DexPrepare.cpp 中 dvmOptimizeDexFile() 方法分析 | /bin/dexopt 源码分析 )(代码片段

Node.js Koa源码解析co@4.6版本源码解析