读书笔记之《深入浅出Node.js》(2)

时间:2021-03-20 09:07:01

上一篇读书笔记中中我们讲到了异步,这里不得不提一下Promise,现在的ES6已经原生支持Promise对象。所谓的Promise对象,其实就是一个异步操作,使用Promise的好处在于,我们可以把异步操作用同步的方式来写,避免了层层嵌套的回调函数(俗称callback hell)。以下的内容主要来自陈天大大的一篇文章讲Promise的文章,然后又加上了Node.js中Promise的一种实现方法。

Promise的状态机

读书笔记之《深入浅出Node.js》(2)
读书笔记之《深入浅出Node.js》(2)
具体代码,这里首先连接数据库,然后打开某一个某,最后插入数据,一个三层的嵌套
读书笔记之《深入浅出Node.js》(2)
用Promise改造后就变成类似同步方法的写法,其中为了实现链式调用,每一个then语句中都要返回一个Promise对象,且最后的catch语句处理之前的then语句中发生的所有异常。
读书笔记之《深入浅出Node.js》(2)
下面是我自己写的一个实现了链式调用Promise且能在得到不想要的结果时退出的一段代码

<script type="text/javascript">
  var getDate = function(url) {
    var promise = new Promise((resolve, reject) => {
      var xhr;
      if (window.XMLHttpRequest) {
        xhr = new XMLHttpRequest();
      } else {
        xhr = new ActiveXObject("Microsoft.XMLHTTP");
      }
      xhr.onreadystatechange = handler;
      xhr.open("GET", url);
      xhr.send(null);
      function handler() {
        if (this.readyState === this.DONE) {
          if (this.status === 200) {
            resolve(this);
          } else {
            reject(this);
          }
        }
      }
    });
    return promise;
  }
  getDate("http://127.0.0.1:3000/data").then(function (data) {
    //判断结果,若不是Frontend,则直接抛出异常,跳转到最后的catch语句中
    if (data.response !== "Frontend") {
      throw "Error";
    }
    console.log('Correct');
    //再次返回一个Promise对象,实现链式调用
    return getDate(data.responseURL)
  }).then(function (data) {
    console.log(data.response);
  }).catch(function (err) {
    console.log(err);
  });
</script>

第五章 内存控制

背景

因为Node.js处于服务器端,所以内存的问题就必须重视起来,这里与浏览器中很少会碰到内存的一些问题,很多时候当发生垃圾回收造成页面加载缓慢时,用户一般就不耐烦地刷新了页面。但是在Node.js中,面对海量请求的时候,哪怕是一丁点的内存泄漏也会越积越多,最后造成服务器的崩溃!

介绍

首先,Node.js使用V8作为执行引擎,所有它的内存管理是交给V8来实现的。Node.js中的内存分为两部分:

  • V8分配的内存(受内存管理的限制),在32位系统下默认为0.7GB,64位系统下默认为1.4GB,但这个限制可以在启动Node时改变。
    node --max-old-space-size=2400 app.js //指定老生代内存大小,单位为MB
    node --max-new-space-size=1024 app.js //指定新生代内存大小,单位为KB

  • Node自行分配的内存(例如Buffer对象,不受到V8内存管理的限制,在处理几个GB的大文件时特别有用)。
    同时在V8中,内存被分为两部分:

    • 老生代(占比98%,存放存活时间较长的对象 ,应用Mark-Sweep和Mark-Compact算法)
    • 新生代(占比2%,存放存活时间较短的对象,应用半空间复制算法Scavenge)

V8中一次小的垃圾回收要50ms,一次大的非增量的垃圾回收要1s以上,这是几乎无法忍受的,所以它内部通过各种算法来优化垃圾回收,算法细节有兴趣的可以自行Google。

我们可以通过分析V8执行时的日志来查看垃圾回收所占用的时间,用如下的方法来启动Node
读书笔记之《深入浅出Node.js》(2),这样在根目录中会生成对应的日志文件,如下图所示:
读书笔记之《深入浅出Node.js》(2)
对应的GC.js文件的代码

for (var i = 0; i < 1000000; i++) {
  var a = {};
}
for (var j = 0; j < 1000000; j++) {
  var b = {};
}

然后在安装了tick模块后,我们就可以看到垃圾回收实际产生的影响了。这里我是在WebStrom中直接用集成好了的工具来做的,省时省力。
读书笔记之《深入浅出Node.js》(2)
分析结果如下图
读书笔记之《深入浅出Node.js》(2)
从结果里可以看到,由于不断分配对象,垃圾回收所占的比列为1%(我的Node版本是6.2.0,已经对垃圾回收做了很多优化),这意味着事件循环每执行1000ms,就要抽出10ms的事件来执行垃圾回收。

高效使用内存

为了高效使用内存,我们在开发中要注意几点:

  • 减少全局变量的使用,通过重新赋值(null或undefined)来释放全局变量,虽然删除对象也可以取得相同的效果,但删除对象有可能干扰到V8的垃圾回收机制,所以采用重新赋值的方法来解除占用会更好。
  • 减少闭包的使用
  • 不要把内存当做缓存,这样会干扰垃圾回收,且还要自己制定缓存的过期策略,推荐使用Redis或者Memcached。

大内存应用

在需要操作大文件时,由于V8内存的限制,不能直接通过fs.readFile()和fs.writeFile()进行操作,而是需要用到stream模块或者继承stream的模块。代码如下

var fs = require('fs');
var reader = fs.createReadStream('../../public/v8_log.log');
var writer = fs.createWriteStream('../../public/v8_log_copy.log');
reader.on('data', function (chunk) {
  writer.write(chunk);
});
reader.on('end',function(){
  writer.end();
});