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了。

时间: 2024-10-20 13:43:41

node js co分析2的相关文章

node js 处理时间分析

结论: pm2日志中请求时间包含连接建立时间到处理结束时间. 今天遇到一个诡异的问题.同样的一个nodejs程序部署在两个环境中, 一个环境的pm2打印的服务器响应时间是几个毫秒,另外一个环境的Pm2打印的服务器响应时间是几十/几百甚至几千毫秒不等.两个环境的部署服务器配置一致,软件环境一致,唯一不同是A环境采用的是nginx做反向代理,B环境采用的是青云的负载均衡器做反向代理. 最后定位到两个日志中响应时间不同的原因是,nginx作为反向代理是会把HTTP请求全部收完之后再发送,而青云的负载均

node js co分析1

转载请注明: TheViper http://www.cnblogs.com/TheViper 更好的可以看http://purplebamboo.github.io/2014/05/24/koa-source-analytics-1/ co模块是什么? 不同于promise,它是TJ大神基于ECMAScript 6 generator的异步控制解决方案. 和promise一样,用co写出来的异步代码很优雅,一目了然,扩展性也很强. 由于需要ECMAScript 6环境,所以需要下载版本新点的n

node.js开发实战

1. 什么是RPC调用(远程过程调用) 二进制协议 . 更小的数据包体积 . 更快的编解码速率 2. Buffer 编解码二进制数据包 (Protocol Buffers)用来编码二进制数据 3. net 搭建多路复用的RPC通道 4. HTTP 服务性能测试 压力测试工具(ab.webbench) 通常用ab (https://www.jianshu.com/p/43d04d8baaf7) 找到性能瓶颈 . top (cpu.内存) . iostat (硬盘) 5. Node.js性能分析工具

高性能Web服务端 PHP vs Node.js vs Nginx-Lua 的对比分析

1. ngx_lua nodejs php 比较 我在研究一阵子ngx_lua之后发现lua语法和js真的很像,同时ngx_lua模型也是单线程的异步的事件驱动的,工作原理和nodejs相同,代码甚至比nodejs的异步回调更好写一些. 性能测试,100并发php:17400nodejs:31197ngx_lua:32628 单纯做http代理服务器加上一些简单的逻辑,似乎ngx_lua的方案更加合适. 引自: PHP vs Node.js vs Nginx-Lua 以下是从占用的资源上来分析:

node.js基础模块http、网页分析工具cherrio实现爬虫

node.js基础模块http.网页分析工具cherrio实现爬虫 一.前言      说是爬虫初探,其实并没有用到爬虫相关第三方类库,主要用了node.js基础模块http.网页分析工具cherrio. 使用http直接获取url路径对应网页资源,然后使用cherrio分析. 这里我主要学习过的案例自己敲了一遍,加深理解.在coding的过程中,我第一次把jq获取后的对象直接用forEach遍历,直接报错,是因为jq没有对应的这个方法,只有js数组可以调用. 二.知识点    ①:supera

用node.js对一个英语句子分析页面进行一个小爬虫

最近遇到一个需求,就是要从一个英语句子分析的页面中,根据你输入的英语从句,点击开始分析按钮,这个页面就会将分析的结果解析出来,如 然后我们就是需要从这个页面中把这些解析好的数据(包括句子语法结构详解,句子相关词汇解释等)取出来,这时候我就想到之前学过node.js,这时候就来弄下node.js的小小的爬虫. 首先,电脑要先安装node.js,至于怎么安装,请google,或者找相关教程来看. 然后就需要了解下node,现在我先加载http模块,然后设置url的值,url就是你要爬的那个网页的地址

Node.js内存泄漏分析

在极客教育出版了一个视频是关于<Node.js 内存泄漏分析>,本文章主要是从内容上介绍如何来处理Node.js内存异常问题.如果希望学习可前往极客学院: 本文章的关键词 - 内存泄漏 - 内存泄漏检测 - GC分析 - memwatch 文章概要 由于内存泄漏在Node.js中非常的常见,可能在浏览器中应用javascript时,对于其内存泄漏不是特别敏感,但作为服务器语言运行时,你就不得不去考虑这些问题.由于很小的逻辑可能导致服务器运行一天或者一个星期甚至一个月才会让你发现内存不断上涨,而

Node.js开发入门—HelloWorld再分析

在Node.js开发入门(1)我们用http模块实现了一个简单的HelloWorld网站,这次我们再来仔细分析下代码,了解更多的细节. 先看看http版本的HelloWorld代码: 代码就是这么简单: // 引入http模块 var http = require("http"); // 创建server,指定处理客户端请求的函数 http.createServer( function(request, response) { response.writeHead(200, {&quo

web前端课程技术总结Node.js 使用方法及相关方法分析

Node.js 使用方法及相关方法分析 首先我们要了解什么是node.js? 官方解释是:node.js是一个基于Chrome v8引擎的javascript 运行环境.Node.js使用了一个事件驱动.非阻塞式 I/O的模型,使其轻量又高效.他是由c++编写的 所以速度很快 简单来说 就是一个js 的运行环境,所以他开发用的语言是js语言 ,通过node去编译你的js文件 node.js 的安装 1)npm i -g 全局(电脑只需安装一次) 2)npm i --save -dev /-D/(