语言Node.js开发者最常范的拾壹个谬误

4 圣诞树结构的回调(回调的鬼世界)

  有个旁人连连拿鬼世界般的回调参数来攻击Node,认为在Node中回调嵌套是力不从心防止的。但实际并非如此。这里有许多缓解方法,可以使您的代码看起来相当规整:

  大家来创制三个例子,然后重构它以利用async模块。那些app是贰个简短的前端能源分析工具,它做到上面这一个工作:

  • 自作者批评HTML代码中有多少scripts,stylesheets,images的引用
  • 将检查的结果输出到极限
  • 自作者批评每1个财富的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);
  });

 

JS中的CSP

  在将CSP的答辩运用到JS中,有一部分不行幽默的追究。前边提到的大卫Nolen,他有几个很有意思的体系,包罗Om,以及core.asyncKoa库(node.js)首要透过它的use(..)格局体现了那一点。而其余1个对core.async/Go
CSP API十二分忠实的库是js-csp

  你确实应该去探视那些伟大的类型,看看里面的各样法子和例子,驾驭它们是什么在JS中落到实处CSP的。

 

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工具来行使:

语言 1

  正如你所看到的,bunyan给您提供了关于当前进程的很多管用的音信,那个音信在成品环境中都老大关键。其它多少个便民的机能是您可以将日志输出到三个或多少个流中。

 

三个傻乎乎的FooBar示例

  好了,理论的东西讲得几近了。大家来探望实际的代码:

// 注意:为了简洁,省略了虚构的`multBy20(..)`和`addTo2(..)`异步数学函数

function *foo(token) {
    // 从通道的顶部获取消息
    var value = token.messages.pop(); // 2

    // 将另一个消息存入通道
    // `multBy20(..)`是一个promise-generating函数,它会延迟返回给定值乘以`20`的计算结果
    token.messages.push( yield multBy20( value ) );

    // 转移控制权
    yield token;

    // 从CSP运行中的最后的消息
    yield "meaning of life: " + token.messages[0];
}

function *bar(token) {
    // 从通道的顶部获取消息
    var value = token.messages.pop(); // 40

    // 将另一个消息存入通道
    // `addTo2(..)` 是一个promise-generating函数,它会延迟返回给定值加上`2`的计算结果
    token.messages.push( yield addTo2( value ) );

    // 转移控制权
    yield token;
}

  下边的代码中有几个generator
“processes”,*foo()*bar()。它们都接受并拍卖三个令牌(当然,假设您愿意你可以肆意叫什么都行)。令牌上的性子messages就是咱们的共享消息通道,当CSP运转时它会取得初步化传入的音讯值举办填空(前面会讲到)。

  yield
token
显式地将控制权转移到“下3个”generator函数(循环顺序)。不过,yield
multBy20(value)
yield
addTo2(value)
都是yield2个promises(从那多个虚构的推移统计函数中回到的),这代表generator函数此时是地处停顿状态直到promise落成。一旦promise达成,当前处于控制中的generator函数会卷土重来并三番五次运维。

  无论最终yield会重回什么,上面的例子中yield再次回到的是1个表明式,都代表大家的CSP运维已毕的音讯(见下文)。

  今后大家有多少个CSP process
generators,我们来看看哪些运作它们?使用asynquence:

// 开始一个sequence,初始message的值是2
ASQ( 2 )

// 将两个CSP processes进行配对一起运行
.runner(
    foo,
    bar
)

// 无论接收到的message是什么,都将它传入sequence中的下一步
.val( function(msg){
    console.log( msg ); // 最终返回42
} );

  那只是二个很简单的事例,但本人认为它能很好地用来分解上边的那一个概念。你可以尝试一下(试着改变一些值),那有助于你领会这一个概念并协调出手编写代码!

 

语言 2

  ES6 Generators系列:

  1. ES6
    Generators基本概念
  2. 深切探究ES6 Generators
  3. ES6
    Generators的异步应用
  4. ES6 Generators并发

  如果你早就读过这么些种类的前三篇小说,那么你势必对ES6
generators十三分通晓了。希望你能从中有所收获并让generator发挥它真的的功力。最后大家要追究的那个主旨只怕会让您血脉喷张,让您苦思苦想(说实话,写那篇小说让本身很费脑子)。花点时间看下作品中的这几个事例,相信对你还是很有匡助的。在求学上的投资会让你今后受益无穷。小编一心信任,在以往,JS中那多少个复杂的异步能力将起点于我那边的有个别想方设法。

 

10 使用console.log来debug

  一旦程序出现谬误,你可以省略地在代码中插入console.log来进展debug。难题一蹴即至以往剔除console.log调试语句再持续。

  难点是其他的开发人士(甚至是您自个儿)只怕还会赶上相同的难题而再重新上边的操作。这就是干什么调试模块如debug留存的缘故。你能够在代码中拔取debug
function来代表console.log语句,而且在调节完之后并非删除它们。

  其余开发人士如若蒙受标题必要调剂代码,只要求经过DEBUG环境变量来运维程序即可。

  那一个小的module具有以下优点:

  • 唯有你通过DEBUG环境变量运转程序,否则它不会在支配台出口任何内容。
  • 您可以有选拔地对代码中的一部分进行调节(甚至足以因此通配符来内定内容)。
  • 极端的输出内容有各个不相同的颜料,看起来很舒畅(英文名:Jennifer)。

  来探视官方给出的演示:

  // 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标记,那么:

语言 3

  除了在应用程序中运用它们,你还足以在一些小的modules中行使它并揭发到NPM。与其余一些繁杂的logger不一样,它只承担debugging而且还很好使。

 

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

详解CSP原理(一点点)

  到底怎么着是CSP?说它是”communicating”,”Sequential”,”processes”到底是如何看头呢?

  首先,CSP一词源自于托尼 Hoare所著的“Communicating Sequential
Processes
”一书。里面全是有关CS的说理,若是你对学术方面的东西感兴趣的话,那本书纯属值得一读。作者不用打算以一种令人为难驾驭的,深奥的,总结机科学的不二法门来阐释这么些大旨,而是会以一种轻松的非正式的方法来进展。

  那我们就从”Sequential”先导吧!那有的你应有早就很熟谙了。这是其余一种谈论有关单线程和ES6
generators异步风格代码的格局。大家来回想一下generators的语法:

function *main() {
    var x = yield 1;
    var y = yield x;
    var z = yield (y * 2);
}

  下面代码中的每一条语句都会按顺序三个二个地实践。Yield主要字标明了代码中被卡住的点(只好被generator函数自个儿过不去,外部代码不能够围堵generator函数的实施),然则不会改变*main()函数中代码的履行顺序。那段代码很简短!

  接下去大家来商讨一下”processes”。那几个是什么样吧?

  基本上,generator函数有点像一个虚构的”process”,它是大家先后的三个独自的一些,如果JavaScript允许,它完全可以与程序的别的一些并行执行。那听起来似乎有些荒唐!假如generator函数访问共享内存(即,假如它访问除了自身内部定义的一部分变量之外的“自由变量”),那么它就不是二个独立的一些。今后我们若是有一个不访问外部变量的generator函数(在FP(Functional
Programming函数式编程)的辩解中我们将它叫做一个”combinator”),由此从理论上来说它可以在大团结的process中运行,可能说作为协调的process来运转。

  不过大家说的是”processes”,注意那么些单词用的是复数,这是因为会设有八个或多个process在同暂时间运营。换句话说,五个或多少个generators函数会被平放一起来协同工作,常常是为了做到一项较大的职分。

  为啥要用七个独立的generator函数,而不是把它们都放到壹个generator函数里啊?壹个最重点的因由就是:效用和关切点的分开。对于3个职分XYZ来说,倘使你将它表达成子职分X,Y和Z,那么在各种子职责协调的generator函数中来促功能益将会使代码更便于领悟和维护。那和将函数XYZ()拆分成X()Y(),和Z(),然后在X()中调用Y(),在Y()中调用Z()是一律的道理。大家将函数分解成3个个单独的子函数,下落代码的耦合度,从而使程序尤其简单保证。

9 从未有过监视与个性分析

  借使Node应用程序没有监视与本性分析,你将对其运行意况一窍不通。一些很重大的东西如event
loop延迟,CPU负载,系统负荷或内存使用量等您将无法获悉。

  那里有一部分特定的服务可以辅助到您,可以从New
Relic
,
StrongLoop以及Concurix,
AppDynamics等精通到。

  你也可以由此开源模块如look或结成不一致的NPM包和谐来落实。不管接纳哪一类办法,你都要确保始终都能监测到你的主次的周转状态,否则你大概会在半夜接受各样离奇的电话告知您程序又并发如此或那样的题材。

 

总结

  CSP的严重性是将八个或更加多的generator
“processes”连接在共同,给它们贰个共享的通讯信道,以及一种可以在互相间传输控制的形式。

  JS中有无数的库都或多或少地运用了一对一专业的法门来与Go和Clojure/ClojureScript
APIs或语义相匹配。那些库的暗中都具有尤其棒的开发者,对于更为钻探CSP来说他们都以尤其好的财富。

  asynquence准备动用一种不太规范而又希望还可以保留紧要结构的主意。借使没有别的,asynquence的runner(..)可以看成你尝试和读书CSP-like
generators
的入门。

  最好的部分是asynquence
CSP与其余异步作用(promises,generators,流程控制等)在共同干活。如此一来,你便可以掌控一切,使用其余你手头上合适的工具来形成义务,而颇具的这总体都只在1个微小的lib中。

  以往我们已经在那四篇文章中详细探索了generators,作者盼望您可见从中受益并拿走灵感以商讨怎么着改造本人的异步JS代码!你将用generators来创建怎样呢?

 

原文地址:https://davidwalsh.name/es6-generators

5 创造多个大而完整的应用程序

  一些初入Node的开发人员往往会将其它语言的局地构思方式融入进来,从而写出分歧风格的代码。例如将兼具的代码写到贰个文件里,而不是将它们分散到温馨的模块中再发布到NPM等。

  就拿大家事先的例证来说,大家将有着的内容都置身壹个文书里,那使得代码很难被测试和读懂。不过别担心,大家会重构代码使其看起来不错并且一发模块化。当然,那也将使得地防止回调鬼世界。

  假设大家将U锐界L 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应用程序模块化,作用越简单越好。

 

异步的runner(..):设计CSP

  因为自个儿一贯在不遗余力探索将互相的CSP情势应用到自个儿本身的JS代码中,所以对于使用CSP来扩展本人要好的异步流程控制库asynquence来说就是一件顺理成章的事。小编写过的runner(..)插件(看上一篇小说:ES6
Generators的异步应用
)就是用来处理generators函数的异步运维的,小编意识它可以很简单被扩展用来拍卖多generators函数在同一时半刻间运行,似乎CSP的章程那样

  小编要消除的率先个规划难题是:如何才能知晓哪些generator函数将取得下贰个控制权?

  要解决种种generators函数之间的消息或控制权的传递,各个generator函数都必须具备3个能让其余generators函数知道的ID,那看起来如同过于蠢笨。经过各类尝试,小编设定了三个大概的循环调度措施。倘若您同盟了多少个generators函数A,B和C,那么A将先拿到控制权,当A
yield时B将接管A的控制权,然后当B yield时C将接管B,然后又是A,以此类推。

  可是怎么样才能实际转移generator函数的控制权呢?应该有3个显式的API吗?作者重新开展了各样尝试,然后设定了一个尤为隐式的法门,看起来和Koa有点类似(完全是以外):每一种generator函数都拿走3个共享”token”的引用,当yield时就代表要将控制权进行更换。

  另二个难题是音讯通道应该长什么样。一种是特别规范的通讯API如core.async和js-csp(put(..)take(..))。但是在作者通过种种尝试之后,作者相比较接济于另一种不太正统的方法(甚至都谈不上API,而只是1个共享的数据结构,例如数组),它看起来就像比较可信的。

  笔者控制采取数组(称之为消息),你可以依照必要控制如何填写和清空数组的故事情节。你能够push()新闻到数组中,从数组中pop()音信,依据预约将差别的信息存放到数组中一定的岗位,并在这个地点存放更扑朔迷离的数据结构等。

  我的迷离是有个别职务急需传递简单的音信,而某些则须求传递复杂的信息,由此不要在一些大致的事态下强制那种复杂度,作者选拔不拘泥于音讯通道的款型而接纳数组(除数组本人外那里没有其他API)。在少数情形下它很不难在附加的花样上对新闻传递机制举办分层,那对我们的话很有用(参见下边的情形机示例)。

  最终,小编发觉那么些generator
“processes”照旧得益于那么些独立的generators可以接纳的异步功效。也等于说,如若不yield控制token,而yield一个Promise(或然三个异步队列),则runner(..)的确会暂停以等待重临值,但不会转移控制权,它会将结果回到给当下的process(generator)而保留控制权。

  最终一点可能是最有争持或与本文中其他库差异最大的(如若小编解释正确的话)。只怕真的的CSP对那么些办法视如草芥,不过自己意识小编的取舍如故很有用的。

 

3 频繁调用回调函数

  很多时候当您保存文件然后再度启航Node web
app时它就神速地崩掉了。最有大概出现的由来就是调用了三遍回调函数,那象征你很只怕在第一回调用之后忘记return了。

  大家创制多个例证来再次出现一下那种地方。小编将开创1个简便的带有基本阐明功用的代办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函数后忘记为止任何运维流程了。那象征倘若我们走访3个不或者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(..);
    }
  });

 

CSP(Communicating Sequential Processes)

  首先,作者写这一多元小说完全是受Nolen
@swannodette精美工作的启示。说真的,他写的拥有小说都值得去读一读。我那边有部分链接可以享用给你:

  好了,让咱们正式启幕对这些主旨的探索。作者不是三个从具有Clojure(Clojure是一种运营在Java平台上的
Lisp
方言)背景转投到JS阵营的程序员,而且小编也未尝任何Go可能ClojureScript的经验。作者发现本身在读这一个小说的时候很快就会错过兴趣,因而小编只可以做过多的试行并从中驾驭到部分得力的事物。

  在那些进度中,作者觉得本身已经有了部分同样的驰念,并追求一致的目的,而那么些都源自于八个不那么鲁钝的思维方法。

  作者尝试创设了二个更简单的Go风格的CSP(以及ClojureScript
core.async)APIs,同时小编期望能保存大多数的平底功效。恐怕有大神会看到自己作品中遗漏的地点,这完全有只怕。倘诺真是那样的话,小编希望自身的探索可以赢得进一步的发展和衍变,而小编也将和我们一同来分享那么些进程!

 

8 不利用静态分析工具

  为了不在生产条件中才发现标题,最好的点子是在付出进度中接纳静态分析工具立刻就发现那么些题材。

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

  • 或是的不当。如:禁止在口径表达式中运用赋值语句,禁止利用debugger
  • 强制最佳体验。如:禁止申明两个相同名称的变量,禁止行使arguments.calle
  • 找出秘密的安全题材,如应用eval()或不安全的正则表明式
  • 侦测出或者存在的习性难点
  • 施行同一的作风

  有关ESLint越多的完整规则可以查阅合法文档。如果想在实际上项目中拔取ESLint,你还应有看看它的布署文档

  有关怎样配置ESLint,这里可以找到一些事例。

  其它,那里还有部分相似的工具如JSLintJSHint

  即便您想解析AST(抽象源树或架空语法树)并团结创办静态分析工具,能够参考EsprimaAcorn

 

状态机:Generator协同程序

  最终一个例证:将一个状态机概念为由壹个大概的helper驱动的一组generator协同程序。Demo(注意:在支撑ES6
JavaScript的新颖版的FireFoxnightly或Chrome中查阅generators是怎么行事的)。

  首先,大家定义2个helper来支配有限的图景处理程序。

function state(val,handler) {
    // 管理状态的协同处理程序(包装器)
    return function*(token) {
        // 状态转换处理程序
        function transition(to) {
            token.messages[0] = to;
        }

        // 默认初始状态(如果还没有设置)
        if (token.messages.length < 1) {
            token.messages[0] = val;
        }

        // 继续运行直到最终的状态为true
        while (token.messages[0] !== false) {
            // 判断当前状态是否和处理程序匹配
            if (token.messages[0] === val) {
                // 委托给状态处理程序
                yield *handler( transition );
            }

            // 将控制权转移给另一个状态处理程序
            if (token.messages[0] !== false) {
                yield token;
            }
        }
    };
}

  state(..)
helper为一定的景况值创立了三个delegating-generator包装器,那一个包裹器会自动运维状态机,并在各种情形切换时转移控制权。

  依据惯例,作者主宰利用共享token.messages[0]的岗位来保存大家状态机的日前状态。那代表你能够通过从连串中前一步传入的message来设定早先状态。不过如若没有传到初叶值的话,大家会简单地将首先个情况作为默许的起首值。同样,依据惯例,最后的情状会被假如为false。那很不难修改以契合你协调的急需。

  状态值可以是其他你想要的值:numbersstrings等。只要该值可以被===运算符严刻测试通过,你就可以利用它看成你的意况。

  在上面的言传身教中,作者体现了3个状态机,它可以遵守一定的依次在多个数值状态间进行更换:1->4->3->2。为了演示,那里运用了1个计数器,因而可以落成数十三遍循环转换。当大家的generator状态机到达最后状态时(false),asynquence连串就会像您所期望的那样移动到下一步。

// 计数器(仅用作演示)
var counter = 0;

ASQ( /* 可选:初始状态值 */ )

// 运行状态机,转换顺序:1 -> 4 -> 3 -> 2
.runner(

    // 状态`1`处理程序
    state( 1, function*(transition){
        console.log( "in state 1" );
        yield ASQ.after( 1000 ); // 暂停1s
        yield transition( 4 ); // 跳到状态`4`
    } ),

    // 状态`2`处理程序
    state( 2, function*(transition){
        console.log( "in state 2" );
        yield ASQ.after( 1000 ); // 暂停1s

        // 仅用作演示,在状态循环中保持运行
        if (++counter < 2) {
            yield transition( 1 ); // 跳转到状态`1`
        }
        // 全部完成!
        else {
            yield "That's all folks!";
            yield transition( false ); // 跳转到最终状态
        }
    } ),

    // 状态`3`处理程序
    state( 3, function*(transition){
        console.log( "in state 3" );
        yield ASQ.after( 1000 ); // 暂停1s
        yield transition( 2 ); // 跳转到状态`2`
    } ),

    // 状态`4`处理程序
    state( 4, function*(transition){
        console.log( "in state 4" );
        yield ASQ.after( 1000 ); // 暂停1s
        yield transition( 3 ); // 跳转到状态`3`
    } )

)

// 状态机完成,移动到下一步
.val(function(msg){
    console.log( msg );
});

  应该很简单地跟踪下面的代码来查阅终究发生了怎么。yield
ASQ.after(1000)
来得了那一个generators可以依据需求做任何项目的依据promise/sequence的异步工作,就好像大家在后边所见到的同等。yield
transition(…)
代表什么转移到2个新的气象。上面代码中的state(..)
helper完毕了拍卖yield*
delegation和景色转换的要害办事,然后一切程序的机要流程看起来十一分简易,表述也很鲜明流畅。

 

1 不采取开发工具

  • 自动重启工具nodemon或supervisor
  • 浏览器内的live
    reload工具(当静态财富或views视图发生改变时自动reload页面)

  与其他编程语言如PHP或Ruby不相同,当你改改了源代码后,Node要求再行开动才能使修改生效。在开创Web应用程序时还有一件事会使你放慢脚步,那就是当修改静态财富时刷新浏览器页面。当然你可以耐心地手动来做这个事情,不过那里会有局地更好的化解办法。

另3个事例Toy 德姆o

  让我们来看一个经典的CSP例子,但只是从大家近期已部分某些粗略的觉察开端,而不是从大家平日所说的纯粹学术的角度来展开探讨。

  Ping-pong。一个很有趣的游乐,对吗?也是本人最欢快的活动。

  让大家来设想一下你早就到位了这么些乒乓球游戏的代码,你通过2个循环往复来运作游戏,然后有两片段代码(例如在ifswitch语句中的分支),每一片段代表二个心心相印的玩家。代码运维不奇怪,你的10日游运营起来就像3个乒乓球亚军!

  可是依照大家地点探讨过的,CSP在这边起到了如何的功能吗?就是法力和关切点的分开。那么具体到大家的乒乓球游戏中,这些分离指的就是多个例外的玩家

  那么,大家得以在二个卓殊高的范围上用多个”processes”(generators)来模拟咱们的二十八日游,各个玩家贰个”process”。当大家落到实处代码细节的时候,我们会发将来几个玩家之家存在操纵的切换,大家誉为”glue
code”(胶水代码(译:在微机编程领域,胶水代码也叫粘合代码,用途是贴边那个大概不般配的代码。可以应用与胶合在联名的代码相同的语言编写,也得以用单独的胶水语言编写。胶水代码不落到实处程序须求的其余成效,它一般出现在代码中,使现有的库或然程序在外部函数接口(如Java本地接口)中举办互操作。胶水代码在快捷原型开发环境中极度火速,可以让多少个零件被飞速集成到单个语言如故框架中。)),那几个义务自小编可能需求第⑧个generator的代码,大家得以将它模拟成游戏的裁判

  大家打算跳过各样特定领域的题材,如计分、游戏机制、物理原理、游戏策略、人工智能、操作控制等。那里大家唯一需求关心的有些就是仿照打乒乓球的往来进程(这实际上也意味着了笔者们CSP的支配转移)。

  想看demo的话可以在这里运维(注意:在支撑ES6
JavaScript的摩登版的FireFoxnightly或Chrome中查阅generators是什么工作的)。现在,让大家联合来探视代码。首先,来看看asynquence
sequence长什么样?

ASQ(
    ["ping","pong"], // 玩家姓名
    { hits: 0 } // 球
)
.runner(
    referee,
    player,
    player
)
.val( function(msg){
    message( "referee", msg );

  咱们开首化了一个messages sequence:[“ping”, “pong”]{hits:
0}
。一会儿会用到。然后,大家设置了多少个富含2个processes运维的CSP(相互协同工作):3个*referee()和两个*player()实例。在玩耍停止时最终的message会被传送给sequence中的下一步,作为referee的出口message。上面是referee的贯彻代码:

function *referee(table){
    var alarm = false;

    // referee通过秒表(10秒)为游戏设置了一个计时器
    setTimeout( function(){ alarm = true; }, 10000 );

    // 当计时器警报响起时游戏停止
    while (!alarm) {
        // 玩家继续游戏
        yield table;
    }

    // 通知玩家游戏已结束
    table.messages[2] = "CLOSED";

    // 裁判宣布时间到了
    yield "Time's up!";
}
} );

  那里大家用table来效仿控制令牌以化解大家地点说的这几个特定领域的题目,那样就能很好地来描述当二个玩家将球打回去的时候控制权被yield给另贰个玩家。*referee()中的while循环表示一旦秒表没有停,程序就会平昔yield
table
(将控制权转移给另三个玩家)。当计时器停止时退出while循环,referee将会接管控制权并揭橥”Time’s
up!
“游戏甘休了。

  再来看看*player() generator的兑现代码(大家接纳五个实例):

function *player(table) {
    var name = table.messages[0].shift();
    var ball = table.messages[1];

    while (table.messages[2] !== "CLOSED") {
        // 击球
        ball.hits++;
        message( name, ball.hits );

        // 模拟将球打回给另一个玩家中间的延迟
        yield ASQ.after( 500 );

        // 游戏继续?
        if (table.messages[2] !== "CLOSED") {
            // 球现在回到另一个玩家那里
            yield table;
        }
    }

    message( name, "Game over!" );
}

  第二个玩家将她的名字从message数组的首先个要素中移除(”ping“),然后第三个玩家取他的名字(”pong“),以便他们都能正确地分辨本身(译:注意那里是多少个*player()的实例,在七个分化的实例中,通过table.messages[0].shift()可以赢得各自分裂的玩家名字)。同时三个玩家都保持对共享球的引用(使用hits计数器)。

  当玩家还未曾听到判决说得了,就“击球”并累加计数器(并出口一个message来公告它),然后等待500微秒(如若球以光速运营不占用其他时刻)。假诺游戏还在继承,他们就yield
table到另贰个玩家那里。就是如此。

  在这里可以查看完整代码,从而驾驭代码的各部分是怎么行事的。

 

前言

  随着某个大商店如沃尔玛(Walmart),PayPal等早先运用Node.js,在过去的几年里,Node.js有了长足的增高。越来越多的人初叶采用Node并公布modules到NPM,其提升的进程远超其余用度语言。但是对于Node的眼光你只怕要求有个别年华去适应,特别是那个刚从其余编程语言转型过来的开发人员。

  在本文中小编将谈一谈Node开发者们最常范的部分荒谬以及哪些来幸免那一个错误。有关示例的源代码,你可以从github上获取到。

 

对于多少个generators函数来说我们也得以成功那或多或少

  那即将说到”communicating”了。这几个又是何等吧?就是合营。假诺大家将四个generators函数放在一些协同工作,它们相互之间需求一个通讯信道(不仅仅是访问共享的效率域,而是两个当真的能够被它们访问的独占式共享通讯信道)。这些通信信道是怎么着吗?不管你发送什么内容(数字,字符串等),事实上你都不需求通过信道发送消息来开展通讯。通讯会像合营那样不难,似乎将次第的控制权从四个地方转移到其余一个地方。

  为何必要转移控制?那重假如因为JS是单线程的,意思是说在肆意给定的二个小时部分内只会有3个顺序在运维,而其余程序都地处暂停状态。也等于说其余程序都远在它们各自任务的中间状态,可是只是被中断实施,要求时会复苏并三番五次运维。

  任意独立的”processes”之间可以神奇地展开通讯和合营,那听起来某些不可相信。那种解耦的想法是好的,不过有点不切实际。相反,就像其余1个打响的CSP的贯彻都是对那些难题领域中已存在的、闻名遐迩的逻辑集的故意分解,其中每种部分都被优良设计过由此使得各部分之间都能完美工作。

  可能作者的掌握完全是错的,可是作者还尚无观察其余五个具体的主意,可以让七个随机给定的generator函数可以以某种方式随机地集结在一齐形成CSP对。它们都急需被设计成可以与其余一些共同干活,须要遵从互相间的通讯协议等等。

 

2 阻塞event loop

  由于Node.js是单线程运维的,全数对event
loop的封堵都将使一切程序被封堵。那象征一旦您有三个上千个客户端访问的web
server,并且程序暴发了event
loop阻塞,那么全部的客户端都将远在等候情况而望洋兴叹拿到服务器应答。

  那里有部分事例,你可能会在不留神中应用它们而发出event loop阻塞:

  难点是你会不留心中做了上述的工作,终归将1个装有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。运转之后您应有会在终点看到如下图所示的结果:

语言 4

  前边已经说过,开源modules也接纳了一般的方法来落到实处对应的职能,因而得以放心使用它们:

  通过使用那种技能拓展质量分析,你可以确切地找出代码中的哪一部分会造成延迟。

 

1.1 自动重启工具

  大家中的半数以上人大概都以那般编写和调剂代码的,在编辑器中保存代码,然后在控制台按CT汉兰达L+C键为止使用,随后通过向上键找到从前实施过的启航命令,按回车来再一次启航应用。不过,通过应用上边那些工具得以活动完结应用的重启并简化开发流程:

  那么些工具得以监视代码文件的改动并机关重启服务。上面以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 – 创造3个演示web应用程序
  • watch – 监视文件修改
  • sendevent – sever-sent events
    (SSE),只怕也足以行使websockets来贯彻
  • uglify-js – 用于压缩客户端JavaScript文件
  • ejs – 视图模板

  接下去本身将创建1个简单易行的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,并会在客户端脚本中成立1个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/)。

 

7 没有测试

  没有提供测试的程序不是3个完完全全的顺序。已经有那般多的工具得以协理大家来进展测试,实在没有任何理由不编写测试用例了:

  作为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模块作为2个pre-commit的hook程序可以自行地运营npm
test。

  即使您不显然怎么着来编排测试,能够通过有些在线教程大概在Github中看看那三个流行的Node项目它们是怎么办的:

 

发表评论

电子邮件地址不会被公开。 必填项已用*标注

网站地图xml地图