葡京在线开户Gremlin:图遍历语言

Gremlin简介

Gremlin是Apache TinkerPop
框架下之觊觎遍历语言。Gremlin是一致栽函数式数据流语言,可以让用户用简单之点子发挥复杂的性能图(property
graph)的遍历或询问。每个Gremlin遍历由同样多重步骤(可能存在嵌套)组成,每一样步都于数据流(data
stream)上执行一个原子操作。

Gremlin包括三个基本的操作:

  • map-step
    对数码流中的靶子开展转换;
  • filter-step
    针对数码流中的对象就是实行过滤;
  • sideEffect-step
    对数据流进行计算统计;

以下是Gremlin在有些景象被的具体以:

  • 1.查物色Gremlin朋友之心上人

    g.V().has("name","gremlin").
      out("knows").
      out("knows").
      values("name")
    
  • 2.寻那些由于片单朋友合伙开创的种

    g.V().match(
      as("a").out("knows").as("b"),
      as("a").out("created").as("c"),
      as("b").out("created").as("c"),
      as("c").in("created").count().is(2)).
    select("c").by("name")
    
  • 3.深受起Gremlin的有所上司,直至CEO

    g.V().has("name","gremlin").
      repeat(in("manages")).
    until(has("title","ceo")).
      path().by("name")
    
  • 4.到手Gremlin合作者的衔分布

    g.V().has("name","gremlin").as("a").
      out("created").in("created").
    where(neq("a")).
      groupCount().by("title")
    
  • 5.取Gremlin购买活之系制品列表并排序

    g.V().has("name","gremlin").
      out("bought").aggregate("stash").
      in("bought").out("bought").
    where(not(within("stash"))).
      groupCount().order(local).by(values,decr)
    
  • 6.获排名前十的中坚人物

    g.V().hasLabel("person").
      pageRank().
    by("friendRank").
    by(outE("knows")).
      order().by("friendRank",decr).
      limit(10)
    

葡京在线开户 1

OLTP 和 OLAP遍历

  • 同赖编写,到处运行
    Gremlin遵循“一不良编写,到处运行”的计划性哲学。这表示不但有的TinkerPop启用的图形系统都能够执行Gremlin遍历,而且每个Gremlin遍历都得让评估也实时数据库查询或批处理查询。(前者为誉为在线交易流程(OLTP),后者为喻为在线分析流程(OLAP))。

  • 和谐多种图遍历
    这种普遍性是出于Gremlin遍历机实现之。这种分布式、基于图形的虚拟机了解怎么协调多机器图遍历的执行。好处是,用户不欲上数据库查询语言和域特定的BigData分析语言(例如Spark
    DSL,MapReduce等)。Gremlin是构建基于图的应用程序所不可或缺的,其余全部还付出Gremlin遍历机处理。
    葡京在线开户 2

目录

 

  • 前言
  • 1 不使用开发工具
    • 1.1
      自动还开工具
    • 1.2
      浏览器自动刷新工具
  • 2 阻塞event loop
  • 3
    频繁调用回调函数
  • 4
    圣诞树结构的回调(回调的火坑)
  • 5
    创建一个好而完全的应用程序
  • 6 缺失日志
  • 7 没有测试
  • 8
    不动静态分析工具
  • 9
    从未有过监视与性分析
  • 10
    使用console.log来debug

命令式和声明式遍历

Gremlin遍历可以坐命令式(程序式)方式,声明性(描述性)方式编写,也得蕴涵命令性和声明性的插花方式编写。

  • 命令式编写方式
    落Gremlin合作者的上级名字分布:

    g.V().has("name","gremlin").as("a").
      out("created").in("created").
    where(neq("a")).
      in("manages").
      groupCount().by("name")
    

    一个命令式的Gremlin遍历告诉运行器如何实施遍历中的各国一样步;然后,遍历器分裂到持有的“Gremlin”的合作者(去除Gremlin自己);下一样步,遍历器走及“Gremlin”合作者的上级(managers),最终依据上面的名字进行统计分发。

因而是命令式的Gremlin遍历,就是它肯定地、程序化地告知遍历器“去这里,然后去那里”。

  • 声明式编写方式
    以下使用声明式编写方式实现了一样的结果:

    g.V().match(
      as("a").has("name","gremlin"),
      as("a").out("created").as("b"),
      as("b").in("created").as("c"),
      as("c").in("manages").as("d"),
    where("a",neq("c"))).
      select("d").
      groupCount().by("name")
    

    声明式的Gremlin遍历并无克告遍历器执行其的手续的依次,而是允许每个遍历器从一个(可能嵌套的)模式之成团中选取一个模式来施行。

唯独,声明遍历具有额外的利,它不光使了编译时查询计划器(如命令式遍历),而且要一个周转时查询计划器,根据每个模式的史统计信息选择生一个实践哪个遍历模式

  • 便利那些倾向于减少/过滤大多数数额的模式。

用户可挑选上述提出的计编写好之遍历语句。不管怎样,用户之遍历语句都见面基于实际的执行引擎以及遍历策略traversal
strategies为重写。Gremlin为用户提供灵活性表达好的查询的;图系统啊本着具体启用TinkerPop的数据系统进行有效地评估图遍历提供了灵活性。

前言

  随着有些挺庄如Walmart,PayPal等初步利用Node.js,在过去底几年里,Node.js有了快捷的滋长。越来越多之丁起选Node并揭示modules到NPM,其前进的快远超其它开销语言。不过对此Node的眼光而也许用部分时光去适应,尤其是那些刚由任何编程语言转型过来的开发人员。

  于本文中我拿称一讲Node开发者们最常范的有些谬误和怎样来避免这些错。有关示例的源代码,你可由github直达取得到。

 

无缝嵌入主语言

  • 联主开发语言与图查询语言
    经数据库查询语言(如SQL)被当跟终极以养条件遭受使的编程语言截然不同。因此,经典数据库要求开发人员既设修主编程语言,还要编写数据库相应的询问语言。Gremlin统一了这分,因为遍历可以用支持功能做以及嵌套(主要编程语言都支持)的别样编程语言编写。因此,用户之Gremlin遍历可以用应用程序语言(主语言,Host
    language)编写,并受益于主语言及其工具(例如类型检查,语法高亮,点得等)所提供的助益。目前设有各种Gremlin语言变体,包括:Gremlin-Java,Gremlin-Groovy,Gremlin-Python,Gremlin-Scala等。

  • 示范程序
    比较之下简单栽艺术,高低立判:

    public class GremlinTinkerPopExample {
      public void run(String name, String property) {
    
    Graph graph = GraphFactory.open(...);
    GraphTraversalSource g = graph.traversal();
    
    double avg = g.V().has("name",name).
                   out("knows").out("created").
                   values(property).mean().next();
    
    System.out.println("Average rating: " + avg);
      }
    }
    

public class SqlJdbcExample {
  public void run(String name, String property) {

    Connection connection = DriverManager.getConnection(...)
    Statement statement = connection.createStatement();
    ResultSet result = statement.executeQuery(
      "SELECT AVG(pr." + property + ") as AVERAGE FROM PERSONS p1" +
        "INNER JOIN KNOWS k ON k.person1 = p1.id " +
        "INNER JOIN PERSONS p2 ON p2.id = k.person2 " +
        "INNER JOIN CREATED c ON c.person = p2.id " +
        "INNER JOIN PROJECTS pr ON pr.id = c.project " +
          "WHERE p.name = '" + name + "');

    System.out.println("Average rating: " + result.next().getDouble("AVERAGE")
  }
}

1 不采用开发工具

  • 机关还开工具nodemon或supervisor
  • 浏览器内之live
    reload工具(当静态资源或views视图发生变更时自动reload页面)

  和另外编程语言如PHP或Ruby不同,当您改改了来代码后,Node需要再行起动才会要修改生效。在开立Web应用程序时还闹相同宗事会要你放慢脚步,那就算是当修改静态资源时刷新浏览器页面。当然你可以耐心地手动来开这些业务,不过这里会产生局部又好之解决办法。

参考资料

The Gremlin Graph Traversal Machine and
Language

1.1 自动还开工具

  我们负之多数人或者还是如此编写和调剂代码的,在编辑器中保存代码,然后于决定高仍CTRL+C键停止利用,随后经为上键找到之前实施过的启动命令,按回车来又起动以。不过,通过利用下这些工具得以自动就应用之更开并简化开发流程:

  • nodemon
  • node-supervisor
  • forever

  这些工具得以监视代码文件之改动并机关重新开服务。下面为nodemon为条例来说说什么样使这些家伙。首先通过npm进行全局安装:

npm i nodemon -g

  然后,在巅峰通过nodemon代替node命令来启动以:

# node server.js

$ nodemon server.js
14 Nov 21:23:23 - [nodemon] v1.2.1
14 Nov 21:23:23 - [nodemon] to restart at any time, enter `rs`
14 Nov 21:23:23 - [nodemon] watching: *.*
14 Nov 21:23:23 - [nodemon] starting `node server.js`
14 Nov 21:24:14 - [nodemon] restarting due to changes...
14 Nov 21:24:14 - [nodemon] starting `node server.js`

  对nodemon或node-supervisor来说,在具有都有的选择中,最牛逼的骨子里可以指定忽略的文本或者文件夹。

1.2 浏览器自动刷新工具

  除了上面介绍的电动还开工具外,还产生外的家伙得以帮忙您加快web应用程序的开支。livereload工具允许浏览器在监测到程序变动后活动刷新页面,而不用手动进行刷新。

  其工作之基本原理和点介绍的一般,只是它监测特定文件夹内之修改然后活动刷新浏览器,而无是重开整个服务。自动刷新需要依赖让在页面被流入脚本或者经过浏览器插件来实现。

  这里我非错过介绍如何采取livereload,相反,我以介绍如何通过Node来创造一个般之工具,它用装有下这些功能:

  • 蹲点文件夹着的文件修改
  • 通过server-sent
    events为所有已连续的客户端发送信息,并且
  • 接触一个page reload

  首先我们需要经过NPM来设置项目用之具备乘项:

  • express – 创建一个示范web应用程序
  • watch – 监视文件修改
  • sendevent – sever-sent events
    (SSE),或者为足以使websockets来兑现
  • uglify-js – 用于压缩客户端JavaScript文件
  • ejs – 视图模板

  接下自己以开创一个简单的Express server在前者页面中渲染home视图:

var express = require('express');
var app = express();
var ejs = require('ejs');
var path = require('path');

var PORT = process.env.PORT || 1337;

// view engine setup
app.engine('html', ejs.renderFile);
app.set('views', path.join(__dirname, 'views'));
app.set('view engine', 'html');

// serve an empty page that just loads the browserify bundle
app.get('/', function(req, res) {
res.render('home');
});

app.listen(PORT);
console.log('server started on port %s', PORT);

  因为运用的是Express,所以我们可以拿浏览器自动刷新工具做成一个Express的中等件。这个当中件会attach到SSE
endpoint,并会见在客户端脚论中创造一个view
helper。中间件function的参数是Express的app,以及用被监视的公文夹。于是,我们以下面的代码加到server.js中,放到view
setup之前:

var reloadify = require('./lib/reloadify');
reloadify(app, __dirname + '/views');

  现在/views文件夹着的文件被监视。整个中件看起像下这样:

  var sendevent = require('sendevent');
  var watch = require('watch');
  var uglify = require('uglify-js');
  var fs = require('fs');
  var ENV = process.env.NODE_ENV || 'development';

  // create && minify static JS code to be included in the page
  var polyfill = fs.readFileSync(__dirname + '/assets/eventsource-polyfill.js', 'utf8');
  var clientScript = fs.readFileSync(__dirname + '/assets/client-script.js', 'utf8');
  var script = uglify.minify(polyfill + clientScript, { fromString: true }).code;

  function reloadify(app, dir) {
    if (ENV !== 'development') {
      app.locals.watchScript = '';
      return;
    }

    // create a middlware that handles requests to `/eventstream`
    var events = sendevent('/eventstream');

    app.use(events);

    watch.watchTree(dir, function (f, curr, prev) {
      events.broadcast({ msg: 'reload' });
    });

    // assign the script to a local var so it's accessible in the view
    app.locals.watchScript = '<script>' + script + '</script>';
  }

  module.exports = reloadify;

  你可能已经注意到了,如果运行条件没有叫装置成’development’,那么这当中件什么为未会见做。这象征我们只好在产品环境遭受以欠代码删掉。

  前端JS脚本文件非常简单,它不过当监听SSE的信息并以得的时刻再次加载页面:

  (function() {

    function subscribe(url, callback) {
      var source = new window.EventSource(url);

      source.onmessage = function(e) {
        callback(e.data);
      };

      source.onerror = function(e) {
        if (source.readyState == window.EventSource.CLOSED) return;

        console.log('sse error', e);
      };

      return source.close.bind(source);
    };

    subscribe('/eventstream', function(data) {
      if (data && /reload/.test(data)) {
        window.location.reload();
      }
    });

  }());

  文件eventsourfe-polyfill.js可以从Remy Sharp’s polyfill for
SSE找到。最后好重大的一些凡将变的本子通过下的措施丰富到前者页面/views/homt.html中:

  ...
  <%- watchScript %>
  ...

  现在,当您每次对home.html页面做修改时,浏览器还以自服务器又加载该页面(http://localhost:1337/)。

 

2 阻塞event loop

  由于Node.js是单线程运行的,所有对event
loop的死都拿如整个程序让死。这象征要您闹一个上千个客户端访问的web
server,并且程序来了event
loop阻塞,那么所有的客户端都拿远在等候状态而望洋兴叹取服务器对。

  这里发生有例,你可能会见于未留心中运用其一旦发出event loop阻塞:

  • 使用JSON.parse()函数解析一个挺深的json
  • 尝在后台对一个老酷之公文进行语法高亮显示(如运用Ace或者highlight.js)
  • 一次性输出一个良之情节(例如从child
    process输出git
    log命令的结果)

  问题是您晤面无留神中开了上述的政工,毕竟将一个具备15Mb左右分寸的内容输出并无见面不时来,对也?这可被攻击者发现并最后要你的全体服务器受到DDOS攻击设夭折掉。

  幸运的是若得由此监视event
loop的推迟来检测好。我们可以通过一些特定的解决方案例如StrongOps来贯彻,或者为足以透过一些开源的modules来促成,如blocked。

  这些工具的工作原理是标准地钉每次interval之间所消费的时空然后告诉。时间不同是通过如此的法子来算的:先记下下interval过程遭到A点以及B点的可靠时间,然后用B点的工夫减去A点的日子,再减去interval运行间隔的光阴。

  下面的事例充分验证了何等来贯彻就或多或少,它是这么做的:

  • 取当前时光和坐参数传入的日变量之间的强精度时间价值(high-resolution)
  • 规定于常规状况下interval的event loop的延迟时间
  • 以延迟时间显示成绿色,如果跨越阀值则显得也红色
  • 然后看其实运行的情,每300毫秒执行同一不行非常的运算

  下面是上述示范的源代码:

  var getHrDiffTime = function(time) {
    // ts = [seconds, nanoseconds]
    var ts = process.hrtime(time);
    // convert seconds to miliseconds and nanoseconds to miliseconds as well
    return (ts[0] * 1000) + (ts[1] / 1000000);
  };

  var outputDelay = function(interval, maxDelay) {
    maxDelay = maxDelay || 100;

    var before = process.hrtime();

    setTimeout(function() {
      var delay = getHrDiffTime(before) - interval;

      if (delay < maxDelay) {
        console.log('delay is %s', chalk.green(delay));
      } else {
        console.log('delay is %s', chalk.red(delay));
      }

      outputDelay(interval, maxDelay);
    }, interval);
  };

  outputDelay(300);

  // heavy stuff happening every 2 seconds here
  setInterval(function compute() {
    var sum = 0;

    for (var i = 0; i <= 999999999; i++) {
      sum += i * 2 - (i + 1);
    }
  }, 2000);

   运行方面的代码用设置chalk。运行之后你当会在顶峰看到如下图所展示之结果:

葡京在线开户 3

  前面都说过,开源modules也使用了貌似之不二法门来落实对应之机能,因此好放心使用它们:

  • https://github.com/hapijs/heavy/blob/bbc98a5d7c4bddaab94d442210ca694c7cd75bde/lib/index.js#L70
  • https://github.com/tj/node-blocked/blob/master/index.js#L2-L14

  通过应用这种技术进行性能分析,你可精确地查找来代码中的啊部分会见促成延迟。

 

3 频繁调用回调函数

  很多时当你保存文件然后重新启航Node web
app时它们便快地崩掉了。最有或出现的来由纵然是调用了个别蹩脚回调函数,这表示你老可能以首先涂鸦调用之后忘记return了。

  我们创建一个例证来再现一下这种状态。我以开创一个简练的蕴藏基本证明功能的代理server。要下其而待设置request这个依靠包,运行程序然后访问(如http://localhost:1337/?url=http://www.google.com/)。下面是这个例子的源代码:

  var request = require('request');
  var http = require('http');
  var url = require('url');
  var PORT = process.env.PORT || 1337;

  var expression = /[-a-zA-Z0-9@:%_\+.~#?&//=]{2,256}\.[a-z]{2,4}\b(\/[-a-zA-Z0-9@:%_\+.~#?&//=]*)?/gi;
  var isUrl = new RegExp(expression);

  var respond = function(err, params) {
    var res = params.res;
    var body = params.body;
    var proxyUrl = params.proxyUrl;

    res.setHeader('Content-type', 'text/html; charset=utf-8');

    if (err) {
      console.error(err);
      res.end('An error occured. Please make sure the domain exists.');
    } else {
      res.end(body);
    }
  };

  http.createServer(function(req, res) {
    var queryParams = url.parse(req.url, true).query;
    var proxyUrl = queryParams.url;

    if (!proxyUrl || (!isUrl.test(proxyUrl))) {
      res.writeHead(200, { 'Content-Type': 'text/html' });
      res.write("Please provide a correct URL param. For ex: ");
      res.end("<a href='http://localhost:1337/?url=http://www.google.com/'>http://localhost:1337/?url=http://www.google.com/</a>");
    } else {
      // ------------------------
      // Proxying happens here
      // TO BE CONTINUED
      // ------------------------
    }
  }).listen(PORT);

  除代理本身他,上面的代码几乎涵盖了颇具需要之有的。再细致瞧下面的内容:

request(proxyUrl, function(err, r, body) {
if (err) {
    respond(err, {
    res: res,
    proxyUrl: proxyUrl
    });
}

respond(null, {
    res: res,
    body: body,
    proxyUrl: proxyUrl
});
});

  于回调函数中,我们发误处理的逻辑,但是于调用respond函数后忘记停止一切运行流程了。这表示要我们看一个无法host的站点,respond函数将见面吃调用两不成,我们会于终端收到下面的错误信息:

  Error: Can't set headers after they are sent.
      at ServerResponse.OutgoingMessage.setHeader (http.js:691:11)
      at respond (/Users/alexandruvladutu/www/airpair-2/3-multi-callback/proxy-server.js:18:7)

This can be avoided either by using the `return` statement or by wrapping the 'success' callback in the `else` statement:

  request(.., function(..params) {
    if (err) {
      return respond(err, ..);
    }

    respond(..);
  });

  // OR:

  request(.., function(..params) {
    if (err) {
      respond(err, ..);
    } else {
      respond(..);
    }
  });

 

4 圣诞树结构的回调(回调的炼狱)

  有些人总是拿地狱般的回调参数来抨击Node,认为以Node中回调嵌套是力不从心避免的。但实质上并非如此。这里有为数不少解决措施,可以要你的代码看起老规整:

  • 利用流程控制模块如async
  • 使用Promises
  • 使用Generators

  我们来创造一个例子,然后重构它坐运async模块。这个app是一个简便的前端资源分析工具,它做到下面这些干活儿:

  • 检查HTML代码中来些许scripts,stylesheets,images的援
  • 以检查的结果输出及终点
  • 反省各一个资源的content-length并将结果输出及巅峰

  除async模块外,你得设置下面这些npm包:

  • request – 读取页面数据(body,headers等)
  • cheerio – 后台的jQuery(DOM元素选择器)
  • once – 确保回调函数只叫实践同一潮

    var URL = process.env.URL;
    var assert = require(‘assert’);
    var url = require(‘url’);
    var request = require(‘request’);
    var cheerio = require(‘cheerio’);
    var once = require(‘once’);
    var isUrl = new RegExp(/[-a-zA-Z0-9@:%+.~#?&//=]{2,256}.[a-z]{2,4}\b(\/[-a-zA-Z0-9@:%+.~#?&//=]*)?/gi);

    assert(isUrl.test(URL), ‘must provide a correct URL env variable’);

    request({ url: URL, gzip: true }, function(err, res, body) {

    if (err) { throw err; }
    
    if (res.statusCode !== 200) {
      return console.error('Bad server response', res.statusCode);
    }
    
    var $ = cheerio.load(body);
    var resources = [];
    
    $('script').each(function(index, el) {
      var src = $(this).attr('src');
      if (src) { resources.push(src); }
    });
    
    // .....
    // similar code for stylesheets and images
    // checkout the github repo for the full version
    
    var counter = resources.length;
    var next = once(function(err, result) {
      if (err) { throw err; }
    
      var size = (result.size / 1024 / 1024).toFixed(2);
    
      console.log('There are ~ %s resources with a size of %s Mb.', result.length, size);
    });
    
    var totalSize = 0;
    
    resources.forEach(function(relative) {
      var resourceUrl = url.resolve(URL, relative);
    
      request({ url: resourceUrl, gzip: true }, function(err, res, body) {
        if (err) { return next(err); }
    
        if (res.statusCode !== 200) {
          return next(new Error(resourceUrl + ' responded with a bad code ' + res.statusCode));
        }
    
        if (res.headers['content-length']) {
          totalSize += parseInt(res.headers['content-length'], 10);
        } else {
          totalSize += Buffer.byteLength(body, 'utf8');
        }
    
        if (!--counter) {
          next(null, {
            length: resources.length,
            size: totalSize
          });
        }
      });
    });
    

    });

  上面的代码看起还不是特意不好,不过你还足以嵌套更老的回调函数。从底部的代码中公当力所能及鉴别出什么是圣诞树结构了,其代码的缩进看起像是样子:

        if (!--counter) {
          next(null, {
            length: resources.length,
            size: totalSize
          });
        }
      });
    });
  });

  要运行方面的代码,在极端输入下面的授命:

  $ URL=https://bbc.co.uk/ node before.js
  # Sample output:
  # There are ~ 24 resources with a size of 0.09 Mb.

  使用async进行有重构之后,我们的代码看起像下这样:

  var async = require('async');

  var rootHtml = '';
  var resources = [];
  var totalSize = 0;

  var handleBadResponse = function(err, url, statusCode, cb) {
    if (!err && (statusCode !== 200)) {
      err = new Error(URL + ' responded with a bad code ' + res.statusCode);
    }

    if (err) {
      cb(err);
      return true;
    }

    return false;
  };

  async.series([
    function getRootHtml(cb) {
      request({ url: URL, gzip: true }, function(err, res, body) {
        if (handleBadResponse(err, URL, res.statusCode, cb)) { return; }

        rootHtml = body;

        cb();
      });
    },
    function aggregateResources(cb) {
      var $ = cheerio.load(rootHtml);

      $('script').each(function(index, el) {
        var src = $(this).attr('src');
        if (src) { resources.push(src); }
      });

      // similar code for stylesheets && images; check the full source for more

      setImmediate(cb);
    },
    function calculateSize(cb) {
      async.each(resources, function(relativeUrl, next) {
        var resourceUrl = url.resolve(URL, relativeUrl);

        request({ url: resourceUrl, gzip: true }, function(err, res, body) {
          if (handleBadResponse(err, resourceUrl, res.statusCode, cb)) { return; }

          if (res.headers['content-length']) {
            totalSize += parseInt(res.headers['content-length'], 10);
          } else {
            totalSize += Buffer.byteLength(body, 'utf8');
          }

          next();
        });
      }, cb);
    }
  ], function(err) {
    if (err) { throw err; }

    var size = (totalSize / 1024 / 1024).toFixed(2);
    console.log('There are ~ %s resources with a size of %s Mb.', resources.length, size);
  });

 

5 创建一个百般如完好的应用程序

  一些初入Node的开发人员往往会以其它语言的局部思考模式融入进,从而写有不同风格的代码。例如将具有的代码写及一个文书里,而不是用她分散到自己之模块中重新宣布到NPM等。

  就用我们事先的例子来说,我们拿装有的始末都位于一个文本里,这让代码很不便被测试与朗诵懂。不过弯担心,我们见面重构代码使其拘禁起不错又愈来愈模块化。当然,这为以行地避免回调地狱。

  如果我们将URL validator,response
handler,request功能块及resource处理程序抽出来放到它们自己之模块中,我们的主程序看起会如下这样:

  // ...
  var handleBadResponse = require('./lib/bad-response-handler');
  var isValidUrl = require('./lib/url-validator');
  var extractResources = require('./lib/resource-extractor');
  var request = require('./lib/requester');

  // ...
  async.series([
    function getRootHtml(cb) {
      request(URL, function(err, data) {
        if (err) { return cb(err); }

        rootHtml = data.body;

        cb(null, 123);
      });
    },
    function aggregateResources(cb) {
      resources = extractResources(rootHtml);

      setImmediate(cb);
    },
    function calculateSize(cb) {
      async.each(resources, function(relativeUrl, next) {
        var resourceUrl = url.resolve(URL, relativeUrl);

        request(resourceUrl, function(err, data) {
          if (err) { return next(err); }

          if (data.res.headers['content-length']) {
            totalSize += parseInt(data.res.headers['content-length'], 10);
          } else {
            totalSize += Buffer.byteLength(data.body, 'utf8');
          }

          next();
        });
      }, cb);
    }
  ], function(err) {
    if (err) { throw err; }

    var size = (totalSize / 1024 / 1024).toFixed(2);
    console.log('\nThere are ~ %s resources with a size of %s Mb.', resources.length, size);
  });

  而request功能块则看起如这样:

  var handleBadResponse = require('./bad-response-handler');
  var request = require('request');

  module.exports = function getSiteData(url, callback) {
    request({
      url: url,
      gzip: true,
      // lying a bit
      headers: {
        'User-Agent': 'Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/38.0.2125.111 Safari/537.36'
      }
    }, function(err, res, body) {
      if (handleBadResponse(err, url, res && res.statusCode, callback)) { return; }

      callback(null, {
        body: body,
        res: res
      });
    });
  };

  完整的例子可以自github
repo中找到。

  现在即使简单了,代码更加爱读,我们呢可开始也咱的app添加测试用例了。当然,我们还足以延续重构代码用获response长度的效果独立分离出来放到自己的模块中。

  好之某些是Node鼓励大家编写小的模块并发布暨NPM。在NPM中您可找到各种各样的模块小到要以interval间大成仍机数的模块。你应当尽力要你的Node应用程序模块化,功能进一步简单越好。

 

6 差日志

  很多Node教程都见面显得示例代码,并当中间不同的地方含有console.log,这给众多Node开发人员留下了一个印象,即console.log就是于Node代码中贯彻日志功能。

  在编排Node
apps代码时你当以有比console.log更好的家伙来促成日志功能,因为这些工具:

  • 针对有些充分而复杂的目标不需要动用util.inspect
  • 置于序列化器,如对errors,request和response对象等开展序列化
  • 支持多种不同的日志源
  • 但自行包含hostname,process id,application name等
  • 支撑不同级别之日记(如debug,info,error,fatal等)
  • 部分尖端功能如日志文件自动滚动等

  这些效应都可免费用,你可以当生育环境遭受采用日志模块如bunyan。如果以模块安装到全局,你还可以获一个利于的CLI开发工具。

  让咱们来看看它的示范程序因询问怎么利用她:

  var http = require('http');
  var bunyan = require('bunyan');

  var log = bunyan.createLogger({
    name: 'myserver',
    serializers: {
      req: bunyan.stdSerializers.req,
      res: bunyan.stdSerializers.res
    }
  });

  var server = http.createServer(function (req, res) {
    log.info({ req: req }, 'start request');  // <-- this is the guy we're testing
    res.writeHead(200, { 'Content-Type': 'text/plain' });
    res.end('Hello World\n');
    log.info({ res: res }, 'done response');  // <-- this is the guy we're testing
  });

  server.listen(1337, '127.0.0.1', function() {
    log.info('server listening');

    var options = {
      port: 1337,
      hostname: '127.0.0.1',
      path: '/path?q=1#anchor',
      headers: {
        'X-Hi': 'Mom'
      }
    };

    var req = http.request(options, function(res) {
      res.resume();
      res.on('end', function() {
        process.exit();
      })
    });

    req.write('hi from the client');
    req.end();
  });

  以顶峰运行,你会视下的出口内容:

  $ node server.js
  {"name":"myserver","hostname":"MBP.local","pid":14304,"level":30,"msg":"server listening","time":"2014-11-16T11:30:13.263Z","v":0}
  {"name":"myserver","hostname":"MBP.local","pid":14304,"level":30,"req":{"method":"GET","url":"/path?q=1#anchor","headers":{"x-hi":"Mom","host":"127.0.0.1:1337","connection":"keep-alive"},"remoteAddress":"127.0.0.1","remotePort":61580},"msg":"start request","time":"2014-11-16T11:30:13.271Z","v":0}
  {"name":"myserver","hostname":"MBP.local","pid":14304,"level":30,"res":{"statusCode":200,"header":"HTTP/1.1 200 OK\r\nContent-Type: text/plain\r\nDate: Sun, 16 Nov 2014 11:30:13 GMT\r\nConnection: keep-alive\r\nTransfer-Encoding: chunked\r\n\r\n"},"msg":"done response","time":"2014-11-16T11:30:13.273Z","v":0}

  开发过程中不过将她看成一个CLI工具来行使:

葡京在线开户 4

  正而您所观看底,bunyan给您提供了关于当前经过的森实惠之音,这些信以产品环境遭受还非常要。另外一个利于之效益是若可拿日志输出到一个要么多个流中。

 

7 没有测试

  没有供测试的程序不是一个完完全全的顺序。已经来如此多的工具得以帮助我们来拓展测试,实在没有任何理由不修测试用例了:

  • 测试框架:mocha,
    jasmine,
    tape,还有多别样框架
  • Asertion(断言)模块:chai,
    should.js
  • 模块mocks, spies, stubs或fake timers如sinon
  • 代码覆盖工具:istanbul,
    blanket

  作为NPM模块的预定,你待在package.json中指定测试命令,如:

  {
    "name": "express",
    ...
    "scripts": {
      "test": "mocha --require test/support/env --reporter spec --bail --check-leaks test/ test/acceptance/",
      ...
   }

  然后经过npm test来启动测试,你根本未用去随便什么来调用测试框架。

  另外一个而需要考虑的是于提交代码之前须要拥有的测试用例都经,这仅待经一行简单的下令就可做到:

npm i pre-commit --save-dev

  当然你吧得强制执行某个特定的code
coverage级别之测试而推辞提交那些不听命该级别之代码。pre-commit模块作为一个pre-commit的hook程序可以活动地运行npm
test。

  如果您免确定什么来修测试,可以由此有在线教程或者以Github中看看那些流行的Node项目它们是怎样做的:

  • express
  • loopback

  • ghost

  • hapi

  • haraka

 

8 不动静态分析工具

  为了不在产环境面临才察觉问题,最好之措施是当开发过程被利用静态分析工具这就意识这些题目。

  例如,ESLint工具得以扶持我们缓解许多题材:

  • 莫不的谬误。如:禁止以尺度表达式中使赋值语句,禁止使用debugger
  • 强制最佳体验。如:禁止声明多独一样名称的变量,禁止用arguments.calle
  • 找有地下的平安问题,如用eval()或无安全的正则表达式
  • 侦测出可能是的特性问题
  • 实践同样的风骨

  有关ESLint更多的共同体规则可查官方文档。如果想在其实项目被应用ESLint,你还该看它的布置文档。

  有关如何配置ESLint,这里足找到有事例。

  另外,这里还有一些形似的工具如JSLint和JSHint。

  如果您想解析AST(抽象源树或架空语法树)并友好创建静态分析工具,可以参照Esprima或Acorn。

 

9 并未监视以及性分析

  如果Node应用程序没有监视及性分析,你拿对其运转情况一无所知。一些杀要紧的东西如event
loop延迟,CPU负载,系统负荷或外存使用量等您用无法获悉。

  这里产生有一定的服务可以帮助到公,可以由New
Relic,
StrongLoop以及Concurix,
AppDynamics顶了解及。

  你为堪经开源模块如look或组合不同的NPM包和谐来兑现。不管选择哪种方式,你还如保管始终都能够监测及公的次第的运行状态,否则你也许会见当半夜收受各种离奇的电话报你程序同时起如此或那样的题材。

 

10 使用console.log来debug

  一旦程序出现谬误,你可简简单单地在代码中插console.log来进展debug。问题迎刃而解后去console.log调试语句再度累。

  问题是另外的开发人员(甚至是您自己)可能还会逢相同的题材如果重重上面的操作。这就是是干吗调试模块如debug是的原故。你得当代码中动用debug
function来替console.log语句,而且当调节完毕以后并非去其。

  其他开发人员如果遇到题目需调剂代码,只待通过DEBUG环境变量来启动程序即可。

  这个微之module具有以下优点:

  • 除非你通过DEBUG环境变量启动程序,否则其不见面在控制高出口任何内容。
  • 若得出选择地指向代码中之同一组成部分开展调剂(甚至可以通过通配符来指定内容)。
  • 极限的输出内容有各种不同之颜料,看起十分舒畅。

  来探官方给起底以身作则:

  // app.js
  var debug = require('debug')('http')
    , http = require('http')
    , name = 'My App';

  // fake app

  debug('booting %s', name);

  http.createServer(function(req, res){
    debug(req.method + ' ' + req.url);
    res.end('hello\n');
  }).listen(3000, function(){
    debug('listening');
  });

  // fake worker of some kind

  require('./worker');

<!--code lang=javascript linenums=true-->

  // worker.js
  var debug = require('debug')('worker');

  setInterval(function(){
    debug('doing some work');
  }, 1000);

  如果因为node
app.js来启动程序,不会见输出任何内容。但是一旦开行的下带在DEBUG标记,那么:

葡京在线开户 5

  除了以应用程序中利用它,你还得在片粗之modules中采用她并揭示到NPM。与外一些犬牙交错的logger不同,它才当debugging而且还生好而。

 

原稿地址:https://www.airpair.com/node.js/posts/top-10-mistakes-node-developers-make