首页 > 代码库 > node js co分析2

node js co分析2

转载请注明: TheViper http://www.cnblogs.com/TheViper 

更好的可以看http://purplebamboo.github.io/2014/05/24/koa-source-analytics-2/

源码

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 = hasCallback ? args.pop() : error;      gen = fn.apply(this, args);    } else {      done = done || error;    }    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;      // ok      if (!err) {        try {          ret = gen.next(res);        } catch (e) {          return exit(e);        }      }      // done      if (ret.done) return exit(null, ret.value);      // normalize      ret.value =http://www.mamicode.com/ toThunk(ret.value, ctx);      // run      if (‘function‘ == typeof ret.value) {        var called = false;        try {          ret.value.call(ctx, function(){            if (called) return;            called = true;            next.apply(ctx, arguments);          });        } catch (e) {          setImmediate(function(){            if (called) return;            called = true;            next(e);          });        }        return;      }    }  }}

var isGenFun = isGeneratorFunction(fn);,判断是不是generator function,其实这个从例子看来基本上都是。然后判断有没有回调。即是不是有

co(function *(){  var results = yield *foo();  console.log(results);  return results;})(function(err,res){    console.log(‘res‘)});

里面的function(err,res){...},如果有,将其赋值给done.然后是gen = fn.apply(this, args);,也就是上一篇里面说generator时候的var it = start();,start是一个generator function,这时,函数还没有执行。

然后是next();,ret = gen.next(res);,开始运行*foo()里面的第一个yield size(),返回形如{value:[function],done:false}的对象。

function *foo(){  var a = yield size(‘node_modules/thunkify/.npmignore‘);  var b = yield size(‘node_modules/thunkify/Makefile‘);  var c = yield size(‘node_modules/thunkify/package.json‘);  return [a, b, c];}

然后ret.done判断是不是完成了。如果完成了,exit()执行回调.可以看到这里向回调传入了两个参数,err是错误信息,res是yield执行返回的结果。

    function exit(err, res) {      setImmediate(function(){        done.call(ctx, err, res);      });    }

如果没有完成,ret.value = http://www.mamicode.com/toThunk(ret.value, ctx);,对yield执行返回的结果格式化一下,

function toThunk(obj, ctx) {  if (isGeneratorFunction(obj)) {    return co(obj.call(ctx));  }  if (isGenerator(obj)) {    return co(obj);  }  if (isPromise(obj)) {    return promiseToThunk(obj);  }  if (‘function‘ == typeof obj) {    return obj;  }  if (isObject(obj) || Array.isArray(obj)) {    return objectToThunk.call(ctx, obj);  }  return obj;}

如果ret.value是generator,继续co(fn),如果是promise,返回thunk形式

function promiseToThunk(promise) {  return function(fn){    promise.then(function(res) {      fn(null, res);    }, fn);  }}

如果是函数,返回函数。

如果是object或array,稍微复杂点,这个最后说。

然后如果ret.value是函数,执行这个函数并传入一个回调函数,

function(){     if (called) return;     called = true;     next.apply(ctx, arguments);}

这也就是为什么给yield传入的函数要写出形如

function size(file) {  return function(fn){    fs.stat(file, function(err, stat){      if (err) return fn(err);      fn(null,stat.size);    });  }}

当这个异步操作执行后,会向fn传入两个参数,因为function next(err, res)。

上一篇里面有一个例子将回调变为一个参数,也就是fn(stat.size)取代fn(null,stat.size),只会返回第一个yield结果,因为这里没有参数移位,只通过参数的位置判断传入的参数所对应的形参,所以stat.size就被认为是err了,也就是直接exit()了,所以定义thunk的时候一定要传入两个参数,而且位置不能变。

顺便说一下上一篇里面提到的thunkify,它是用来将一般异步操作变成thunk形式,源码很简单

function thunkify(fn){  assert(‘function‘ == typeof fn, ‘function required‘);  return function(){    var args = new Array(arguments.length);    var ctx = this;    for(var i = 0; i < args.length; ++i) {      args[i] = arguments[i];    }    return function(done){      var called;      args.push(function(){        if (called) return;        called = true;        done.apply(null, arguments);      });      try {        fn.apply(ctx, args);      } catch (err) {        done(err);      }    }  }};

使用

var size=thunkify(fs.stat);function *foo(){  var a = yield size(‘node_modules/thunkify/.npmignore‘);  var b = yield size(‘node_modules/thunkify/Makefile‘);  var c = yield size(‘node_modules/thunkify/package.json‘);  return [a, b, c];}co(function *(){  var results = yield *foo();  console.log(results);  return results;})();

思想就是把参数添加到args数组,最后再向args数组添加

function(){        if (called) return;        called = true;        done.apply(null, arguments); }

这个回调,作为fn.apply(ctx, args);执行后的回调。里面的done是上面co源码里面的

function(){   if (called) return;   called = true;   next.apply(ctx, arguments);}

done这里就相当于自己写的thunk里面的fn.

thunk的总结下就是在异步操作外面封装一个return function(fn),fn是co向thunk传入的回调,里面有next().然后在异步操作回调里面要触发fn,以保证fn的next()会执行。

回到主线,然后next.apply(ctx,arguments);,执行下一个yield.

最后说一下toThunk()里面的objectToThunk

function objectToThunk(obj){  var ctx = this;  var isArray = Array.isArray(obj);  return function(done){    var keys = Object.keys(obj);    var pending = keys.length;    var results = isArray      ? new Array(pending) // predefine the array length      : new obj.constructor();    var finished;// prepopulate object keys to preserve key ordering    if (!isArray) {      for (var i = 0; i < pending; i++) {        results[keys[i]] = undefined;      }    }    for (var i = 0; i < keys.length; i++) {      run(obj[keys[i]], keys[i]);    }    function run(fn, key) {      if (finished) return;      try {        fn = toThunk(fn, ctx);        if (‘function‘ != typeof fn) {          results[key] = fn;          return --pending || done(null, results);        }        fn.call(ctx, function(err, res){          if (finished) return;          if (err) {            finished = true;            return done(err);          }          results[key] = res;          --pending || done(null, results);        });      } catch (err) {        finished = true;        done(err);      }    }  }}

例子

function *foo(){  var a= yield {    name: {      first: yield size(‘node_modules/thunkify/.npmignore‘),      last:yield size(‘node_modules/thunkify/Makefile‘)    }  };  return a;//{name:{first:13,last:39}}

Object.keys(obj)将oject的所有key添加到一个数组。这里是是[‘name‘],[‘first‘,‘last‘],然后初始化最终返回的results.接着就是run()

    for (var i = 0; i < keys.length; i++) {      run(obj[keys[i]], keys[i]);    }

例子里面有一层嵌套,所以obj[keys[i]]还是对象,这没关系,后面会处理。

注意到fn = toThunk(fn, ctx);,如果fn是对象的话,又会去调用objectToThunk(obj)。....如果fn中有嵌套,toThunk(fn,ctx)会进行深度遍历。

如果返回的fn不是function,则认为确实是嵌套到了尽头,也就是最终函数。--pending判断key是不是key数组的最后一个了,如果是就done(null, results)

如果fn是function,就和上面的next()差不多了,不同的是各个函数执行一次就对公用的长度变量减一,不需要关心各个函数的执行顺序,只要当其中一个函数发现变量变为0时,代表其他函数都执行好了,是最后一个,于是就可以调用回调函数done了。

node js co分析2