zoukankan      html  css  js  c++  java
  • 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 = 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 = 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了。

     

  • 相关阅读:
    关于团体项目技术选型的补充
    关于此次团队项目中技术选型问题
    习题3 怎样与用户有效地沟通以获取用户的真实需求?
    面向过程(或者叫结构化)分析方法与面向对象分析方法到底区别在哪里?请根据自己的理解简明扼要的回答。
    你认为一些军事方面的软件系统采用什么样的开发模型比较合适?
    此次项目之用户手册
    形式化说明技术以及图书流通系统。
    此次项目的需求验证
    随着物流的发展、虚拟技术的发展,实体商场到底以什么样的形式存在(在未来)的呢?
    此次项目的过程模型选择
  • 原文地址:https://www.cnblogs.com/TheViper/p/4211399.html
Copyright © 2011-2022 走看看