OurJS


OurJS-我们的JS, 我们的技术-IT文摘; 专注JS相关领域;
我们热爱编程, 我们热爱技术;我们是高大上, 有品味的码农;

欢迎您订阅我们的技术周刊


我们会向您分享我们精心收集整理的,最新的行业资讯,技术动态,外文翻译,热点文章;
我们使用第三方邮件列表向您推送,我们不保存您的任何个人资料,注重您的隐私,您可以随时退订,

欢迎分享您的观点,经验,技巧,心得

让我们一起找寻程序员的快乐,探索技术, 发现IT人生的乐趣;


本网站使用缓存技术每次加载仅需很小流量, 可在手机中流畅浏览;
如果您发现任何BUG,请即时告知我们: ourjs(at)ourjs.com

Node.JS开发者常犯的10个错误(一)


分享到
分类 编程技巧   关键字 分享   发布 ourjs  1420773582237
注意 转载须保留原文链接,译文链接,作者译者等信息。  
Node.JS在过去几年有着长足的发展。越来越多的人采用基于Node的NPM来发布他们的模块,并且远远超过了其它语言 。然而当你从其它语言转向Node时,需要一些时间才能适合它的哲学。

这篇文章我们将讨论一下Node开发者常见的一些错误,以及避免方法。

1. 不使用开发工具


不像PHP、Ruby等其它语言,node改完代码之后每次都需要重启,在浏览器上表现就是需要刷新页面。你可以手动做这些事情,但这会降低你的开发速度。

1.1 自动重启


大多数人会在编辑器中保存文件,单击[CTRL+C],然后手动重启服务,但是你可以使用工具来自动完成这一步。


这些模块会监测文件的改变,然后自动重启你的服务器。首先安装一个

npm i nodemon -g

然后用nodemon代替node运行你的程序

$ 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`

很简单吧

2. 阻塞循环


因为Node.js是在一个进程中运行的,每个阻塞事件循环(Event Loop)的操作就会阻塞一切。当有数千个并连连接时,你阻塞其中一个,其它的都得等待。 注* 参见  Node.js安全教程:防止阻塞Event Loop的潜在攻击 

下面是一些可能发生的情况

  • 解析一个超大的JSON文件
  • 尝试为一个超大的源代码文件添加代码高亮(比如使用 Ace  或 highlight.js
  • 分析一个超大的命令行输出。(比如在子进程child process中运行git log)

这些攻击可能是在你无意之间就会发生的,因为解析一个15Mb大的文件并不是经常碰到的,但是在DDOS攻击时这一个漏洞就足够了。

幸运的是你可以通过一些属性或第三方模块检测到事件循环的阻塞或延时,像StrongOpsblocked

这些工具的基本原理就是计算一个interval定时器两次间隔响应的时间,如果显著变长,就证明当前的EventLoop被阻塞或是被攻击了。下面是一段示例代码。


  var getHrDiffTime = function(time) {
    // ts = [seconds, nanoseconds]
    var ts = process.hrtime(time);
    // 将秒转换成豪秒
    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);

  // 这里模拟了一个密集CPU运算,每2秒运行一次
  setInterval(function compute() {
    var sum = 0;

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


3 同一个回调被运行很多次


有多少次你保存完文件之后,看到服务很快地挂掉了?最常见的情况就是你运行了两次回调。或者说在第一次时你忘记了返回。下面是一个代理服务器的例子:


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, res, body) {
  if (err) {
    respond(err, {
        res: res,
        proxyUrl: proxyUrl
    });

    //此时response.end() 己被调用,但忘记了返回
  }

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



注* 即在一个MiddleWare或Filter中将响应关闭后 response.end() ,又在另外一个回调中运行了一个response.end() 此时会发生异常。


4 回调地狱(Callback Hell)


Callback Hell是node程序经常被抨击的一点,在NodeJS中回调嵌套是无法避免的,但是你可以使用一些工具保持你代码的优美和整洁:

  • 使用流程控制模块 (像 async
  • Promises模式

除了async模块,下面的例子还用到了其它几个

  • 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
          });
        }
      });
    });
  });

使用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);
  });



原文地址: 点此
社区评论 ( Beta版 )
OnceDoc 您自己的企业内容管理系统——文档、流程、知识库、报表、网盘All In One

访问404页面,寻找丢失儿童
 热门文章 - 分享最多
  1. 2014年最受欢迎的前十大语言:JavaScript、PHP、Java排前三
  2. 主流JavaScript MVC框架性能比较测试:Angular vs Backbone vs Ember
  3. AngularJS在大型单页面应用中的性能优化(一)
  4. CSS3实现的响应式字体:自适应视图窗口大小的新单位
  5. JavaScript代码组织结构良好的5个特点[reuqire.js]
  6. io.js新图标logo征集中
  7. 5个现在就该使用的数组Array方法: indexOf/filter/forEach/map/reduce详解
  8. AngularJS在大型单页面应用中的性能优化(二)
  9. 在JavaScript的Array数组中调用一组Function方法
  10. WebPack:更优秀的模块依赖管理工具,及require.js的缺陷
  11. AirJD-简单好用的免费建站工具

 相关阅读 - 编程技巧
  1. CSS3实现的响应式字体:自适应视图窗口大小的新单位
  2. 更快地定位DOM(HTML)元素的方法(Rails)
  3. Go语言实例教程基础语法:数组操作篇(二)
  4. 一些你不知道的JavaScript Console调试命令
  5. Go语言实例教程基础语法篇(一)
  6. 如何选择Node.js Web开发框架?
  7. 针对特定浏览器起作用的CSS: IE Chrome Firefox CSS Hack
  8. 创造canvas的艺术
  9. 有可能将CSS应用到一个字符的一半吗?
  10. Express.JS指南

 关键字 - 分享
  1. 界面控件包TMS IntraWeb Component Pack Pro Script Edition发布v5.8.8.1
  2. 今年最新的30个Android库,你了解吗?
  3. Kendo UI ASP.NET MVC使用教学视频集锦(高清在线观看)
  4. IntelliJ IDEA使用技巧——关于版本控制(上)
  5. 图表编辑器TMS Diagram Studio更新至v4.14,支持RAD Studio 10.2 Tokyo
  6. Windows网络守门人UserLock教程:让用户、组或组织单位进行同步会话
  7. Kendo UI Grid中的动态数据(三)
  8. IntelliJ IDEA使用技巧——常用快捷键Mac篇
  9. Windows网络守门人UserLock教程:UserLock教程:阻止规定时间以外的访问连接
  10. 一流的报表产品Nevron Chart for SSRS更新v2016.1丨附下载

 欢迎订阅 - 技术周刊

我们热爱编程, 我们热爱技术; 我们是高端, 大气, 上档次, 有品味, 时刻需要和国际接轨的码农; 欢迎您订阅我们的技术周刊; 您只需要在右上角输入您的邮箱即可; 我们注重您的隐私,您可以随时退订.
加入我们吧! 让我们一起找寻码农的快乐,探索技术, 发现IT人生的乐趣;


 关注我们

我们的微信公众号: ourjs-com
打开微信扫一扫即可关注我们:
IT文摘-程序员(码农)技术周刊

ourjs官方微信号