maple_uncle

升级后的Node.js v6 长期支持版 Boron 中的10个主要功能

maple_uncle · 2016-12-21翻译 · 963阅读 原文链接

正如我们之前在 Node.js v6.9.0 发布简报中所述,Node.js v6 的版本规划,本周进入到了长期支持(服务)版。对于 Node.js 和用户来说,这都是重要的一步。这个版本增加了很多有用的功能。

接着你可能想知道,这个最新发布的 v6 长期支持版和 Node.js v4 长期支持版对比来看,那些新增的最好的功能是什么。Luckily!我们已经收集了10个最有用和有趣的新功能,都罗列在下面了。包括像 DevTools Inspector、未处理的 Promise 拒绝警告,还有 进程警告 API。

1. DevTools Inspector 集成

去年,Chromium 团队找到 Node 核心团队,问他们是否对,重新使用和 Blink 捆绑的 DevTools debugger 来和 Node.js 交互的这种方式,感兴趣。尽管Node.js debugger 非常的实用, 这些年它也没有被很好的关注,现代浏览器中的 Javascript debuggers 已经比 Node 自己能够提供的先进很多了。

在 Node.js 6.3.0 中,Google 的 v8_inspector 协议来自 Blink,并随 Node 提供。这个功能仍然被 Node 核心团队视为试验性的,这意味着,它还没有被全面的记录,并且可能在未来的版本中被移除,不必经过淘汰周期。但是呢,鉴于这个工具的普及和强大,上面的情况也并不可能发生。最可能的结果是,旧的 debugger 最终被新功能完全取缔。

当 Node.js 以 --inspect 命令后参数(带可选的端口号)运行的时候,控制台会打印出 chrome-devtools://。在 Chrome 浏览器中输入这个链接,就会直接在进程中发起一个远程调试连接。添加 --debug-brk 命令行捕获EventEmitter上的侦听器的名称捕获EventEmitter上的侦听器的名称参数,就会在你的应用程序的第一行打断点,这样你就能够使用调试器啦。你可以使用 Chrome 的 DevTool 来调试你的 Node 应用程序,就和你调试前端 JavaScript 一样,包括实时代码编辑和完全异步堆栈调用功能等。阅读 Paul Irish 的文章 ,了解 Node.js v6 LTS 现有特性的更多细节。

Node.js using DevTools Inspector

资料来源: Paul Irish 的文章, 使用Chrome DevTools调试Node.js

这个新协议不是Chrome独有的,它是一种WebSockets JSON协议,具有良好的文档,已经在多个客户端和服务器上实现。Visual Studio Code 编辑器已经宣布已经支持了这个实验特性,你也可以在命令行界面去使用它。

2.捕获EventEmitter上的侦听器的名称

eventNames() 方法,在 Node.js 6.0.0 添加, 会返回一个给定 EventEmitter 对象上的,由用户回调监听的,所有事件的名称。除非你使用了内部的 _events 属性,不然以前这种功能是不可用的。

找到正在被监听的事件的名称,对于检测一个事件何时不被监听的,非常有用。这样就允许附加监听器来处理那些还没有被处理的事件,或忽略那些不再需要的针对一些事件的工作。

3. 大修 Buffer 构造函数API

Buffer 构造函数 API被大改啦。废弃了旧的 new Buffer(...) , 使用 Buffer.from()Buffer.alloc() 来完美的替换。这些 API 在 v.5.10.0 的时候增加到了 Node 核心,同时运行两个不同的用法:Buffer.from() 从类数组(比如一个数组,字符串,或者其他 Buffer) 中创建一个 Buffer,Buffer.alloc() 创建一个指定大小的,0填充的 Buffer 。

此外在 v5.10.0,增加了这个 --zero-fill-buffers 命令行标识 ,在一个 Node 应用程序中,让所有的 新创建的 Buffers 都被自动强制的 0填充。

而这个新的 Buffer 构造特征集,提供一个更清晰的接口,来使应用程序不可能意外地通过不正确的缓冲区创建而泄漏旧内存空间.

在 Node.js 文档中,使用普通的 Buffer() 构造函数被遗弃了,同时解释了新的 API 被使用的原因。在 Node.js 未来的版本中,当一个 Buffer 被使用 旧的 构造器被创建的时候,一个警告将被打印到标准错误中。

4. 未处理的 Promise 拒绝警告

对 Promise 经常挑剔的原因之一,就是错误能够容易被吞掉并且忽略。从 io.js 和 Node.js v5 开始,’unhandledRejection’’rejectionHandled’ 事件,被 process 对象发出的,已经可以用来洞悉没有被处理的 Promise rejections。由于 Promise 错误处理的语义,它不可能和 uncaughtException 一样清晰的表示一个 rejection 可能被存储并在最后处理。事实上,对于 unhandledRejection 事件名称,早期的候选之一就是 possiblyUnhandledRejection。但是现在惯用的 Promise 用法表明这是一种反模式,rejection 处理函数应该被放在接近其创建的 Promise 上,而不是直接放在构造器上,或直接放在后面。

自从 Node.js v6.6.0, 这个 unhandledRejection 事件也会引起一个警告会被打印到标准错误上。

$ node
> new Promise((resolve, reject) => { setTimeout(() => { reject(new Error('Whoa!')) }, 100) })
Promise { <pending> }
> (node:35449) UnhandledPromiseRejectionWarning: Unhandled promise rejection (rejection id: 1): Error: Whoa!

这个行为可以在带有 --no-warnings 命令行参数的时候关闭,或者在带有 --trace-warnings 命令行参数的时候创造更多的信息。这样,你就可以追踪错误代码的位置了。

$ node
$ node --trace-warnings
> new Promise((resolve, reject) => { setTimeout(() => { reject(new Error('Whoa!')) }, 100) })
Promise { <pending> }
> (node:35484) UnhandledPromiseRejectionWarning: Unhandled promise rejection (rejection id: 1): Error: Whoa!
    at emitPendingUnhandledRejections (internal/process/promises.js:57:27)
    at runMicrotasksCallback (internal/process/next_tick.js:61:9)
    at _combinedTickCallback (internal/process/next_tick.js:67:7)
    at process._tickDomainCallback (internal/process/next_tick.js:122:9)

由于一个 rejection 处理程序 能够在 Promise 创建之后被 catch() 方法处理,这个警告将不会被发射,直到这个 rejection 之后的事件循环的下一个刻度之后。

$ node
> function resolver (resolve, reject) { setTimeout(() => { reject(new Error('Whoa!')) }, 100) }
undefined
> // rejection handler attached on same tick:
> p = new Promise(resolver); p.catch((err) => { console.error(err) });
Promise { <pending> }
> Error: Whoa!
    at Timeout.setTimeout (repl:1:81)
    at ontimeout (timers.js:365:14)
    at tryOnTimeout (timers.js:237:5)
    at Timer.listOnTimeout (timers.js:207:5)
> // rejection handler added on a later tick, causing an additional ‘rejectionHandled’ event
> p = new Promise(resolver)
Promise { <pending> }
> (node:35560) UnhandledPromiseRejectionWarning: Unhandled promise rejection (rejection id: 1): Error: Whoa!
> p.catch((err) => { console.error(err) });
Promise { <pending> }
> (node:35560) PromiseRejectionHandledWarning: Promise rejection was handled asynchronously (rejection id: 1)
Error: Whoa!
    at Timeout.setTimeout (repl:1:81)
    at ontimeout (timers.js:365:14)
    at tryOnTimeout (timers.js:237:5)
    at Timer.listOnTimeout (timers.js:207:5)

5. 快速和安全的临时目录创建

fs.mkdtemp() API 在 v5.10.0被加到到了 Node 核心中,提供一个有保障的方式来创建一个独一无二的临时目录。这个 API 生成6个随机的字符附加到必须的目录 prefix 参数之后。这个功能以前可能出现在用户创建的模块中,像unique-temp-dir,尽管和使用本地系统调用和不都是有保障的安全相比,这个功能的 JavaScript 实现的所有的痛苦都来自性能问题。

这个 API 允许你结合系统默认的临时目录,来完全确保没有目录冲突。将此作为Node.js中的标准化特性,是保证API对于需要使用临时目录的任何模块或应用程序都是一致的。

6. 定时攻击预防

crypto.timingSafeEqual() API 在 v6.6.0 中被加入到了 Node.js 核心中,来避免定时攻击。这个 API 允许比较,却不会泄漏关于比较的定时信息,而这个信息可能会导致恶意方能给推断被比较的值。通过将这个 API添加到 crypto 模块,来允许 它 在 assert 之外使用。一般说来,如果你需要比较两个值,一个来自用户的输入,另一个是一个秘密(或者来自一个秘密),就可以使用这个 API。

7.进程警告 API

这个新的进程警告 API 是在 v6.0.0 被添加的,并且增加对被 Node.js 发射的进程警告的监听能力,提供一个 API 来覆盖默认的处理函数,例如被用在自定义的日志系统。具体来说,如果你正在使用一个自定义的 JSON 日志。你现在可以捕获 Node 核心警告,并让它们作为 JSON 被记录。

这个 API 还能够被非核心的代码使用,来适当的发射非重大的警告。例如

process.emitWarning('Something Happened!', 'CustomWarning'); 
// 或者
process.emitWarning('This API is deprecated', 'DeprecationWarning');

目前,Node 核心发射的: DeprecationWarning,当 “运行时弃用的” 核心API被使用的时候被使用。PromiseRejectionHandledWarning, 当一个 Promise 被拒绝但是没有拒绝处理程序附加来接受它的时候被使用。* MaxListenersExceededWarning, 当一个 EventListener 有超过 maxListeners 数量(默认10个)的时候被使用,这可能是内存泄漏的提示,监听器被添加却没有在不再需要的时候移除。

警告任然被打印到标准错误,但是也是自定义的用户区错误。

$ node -e 'process.emitWarning("Something Happened!", "CustomWarning");'
(node:33526) CustomWarning: Something Happened!

请注意,警告输出现在也包含这个进程的 ID。

此外,这个 API 还带来了一些新的命令行参数来调整警告输出:

  • --no-warnings 禁止打印到标准错误 (内部的 ’警告’ 事件任然发射)
  • --no-deprecation 禁止打印不赞成使用警告到标准错误 (内部的 ’警告’ 事件任然发射)
  • --trace-warnings 堆栈跟踪打印到标准错误,镜像错误输出,对于从您自己的代码或依赖关系中查找使用已弃用的API的位置很有用。
  • --trace-deprecation 让栈追踪只对反对警告
  • --throw-deprecation 对待一个反对警告作为一个抛出错误
$ node -e 'require("sys")'
(node:33668) DeprecationWarning: sys is deprecated. Use util instead.
$ node --no-deprecation -e 'require("sys")'
$ node --trace-deprecation -e 'require("sys")'
(node:33681) DeprecationWarning: sys is deprecated. Use util instead.
    at sys.js:10:6
    at NativeModule.compile (bootstrap_node.js:497:7)
    at Function.NativeModule.require (bootstrap_node.js:438:18)
    at Function.Module._load (module.js:426:25)
    at Module.require (module.js:497:17)
    at require (internal/module.js:20:19)
    at [eval]:1:1
    at ContextifyScript.Script.runInThisContext (vm.js:25:33)
    at Object.exports.runInThisContext (vm.js:77:17)
    at Object.<anonymous> ([eval]-wrapper:6:22)
$ node --throw-deprecation -e 'require("sys")'
internal/process/warning.js:45
      throw warning;
      ^

DeprecationWarning: sys is deprecated. Use util instead.
    at sys.js:10:6
    at NativeModule.compile (bootstrap_node.js:497:7)
    at Function.NativeModule.require (bootstrap_node.js:438:18)
    at Function.Module._load (module.js:426:25)
    at Module.require (module.js:497:17)
    at require (internal/module.js:20:19)
    at [eval]:1:1
    at ContextifyScript.Script.runInThisContext (vm.js:25:33)
    at Object.exports.runInThisContext (vm.js:77:17)
    at Object.<anonymous> ([eval]-wrapper:6:22)

8.符号链接保存

指示模块加载器在解析和缓存模块时保留符号链接。打开的时候,当设置 __dirname__filename 以及在使用位置,使用 require() 而不是使用链接文件的“realpath”来解析其他模块的路径时,会保留模块路径。

这个功能刚开始讨论的时候,是使用下面行为的例子,当符号链接没有被保存的时候,行为是不工作的。

解析正确:

app
    index.js //require("dep1")
    node_modules
        dep1
            index.js //require("dep2")
        dep2
            index.js //console.log('fun!'):

不解析,用户期望的应该是这个链接在一个恰当的位置

app
    index.js //require("dep1")
    node_modules
        dep1 -> ../../dep1
        dep2
            index.js
dep1
    index.js //require("dep2")

当开发使用对等依赖,可以被相互连接而不是手动复制,才寻求这个行为。

在以为这对生态系统的影响更多的是积极的,这样的错误思想下,保留符号链接在 Node.js v6.0.0 中是默认开启的。不幸的是,通过 bug 回报发现,一些使用案例中,新功能会中断应用程序,或基于 Node 以前的运行情况的假设下,新功能还会导致低性能。你可以在原帖 中了解更多关于这个问题,这个问题在 v6.0.0 发布之后非常活跃,并且如何解决这个汇报的问题是讨论的重点。

最后,--preserve-symlinks 命令行参数被添加,并且在 v6.2.0 中恢复了默认行为。 而核心团队也表明,--preserve-symlinks 现在最好的解决办法,迄今为止,还没有好的建议。

9. 直接通过 Node.j s的 V8 引擎性能分析

这个新的命令后参数 --prof-process 是在 Node.js v5.2.0 增加的,来给 V8 引擎性能简介运行内置的格式化工具。通过使用直接传递给 V8 的 --prof 命令行参数,这些配置已经可以在 Node.js 中使用一段时间了。

当一个程序以 --prof 运行的时候,在应用程序内部,一个例如 isolate-0x102004c00-v8.log(这个八进制数每次运行都会改变) 名字的文件就会为每一个 "isolate"(一个拥有自己的堆的独立的 V8 虚拟机实例) 创建(子进程或者使用了 vm 模块,都可能引起应用程序去使用一个或以上的 isolate)。

可惜的是,这些分析日志文件是人类不可读的,并且非常冗长:

$ node --prof ./map-bench.js
$ wc isolate-0x*.log
    3375    4571  419868 isolate-0x102004c00-v8.log
    3801    4968  514577 isolate-0x102801000-v8.log
    4319    5493  628883 isolate-0x103800a00-v8.log
   11495   15032 1563328 total

V8还附带了它所谓的“tick processor”,它能够解析这些文件并产生人类可读和有用的输出。以前,你可能不得不从 npm 去安装例如 tick,但是,对于作者和用户来说,问题是这个 tick processor 需要跟上 V8 的每个版本,来从 V8 产生的日志文件中得到有用的输出。在 Node.js 中带着一个 tick processor 能够解决这样的需求,并且还为用户带来一个方便有用的代码分析工具。

$ node --prof-process isolate-0x103800a00-v8.log
Statistical profiling result from isolate-0x103800a00-v8.log, (2819 ticks, 201 unaccounted, 0 excluded).

 [Shared libraries]:
   ticks  total  nonlib   name
     17  0.6%        /usr/lib/system/libsystem_platform.dylib
      6   0.2%        /usr/lib/system/libsystem_c.dylib

 [JavaScript]:
   ticks  total  nonlib   name
    112 4.0%    4.0%  Stub: StringAddStub_CheckNone_NotTenured
     47  1.7%    1.7%  Stub: CEntryStub
     43  1.5%    1.5%  LazyCompile: *runFakeMap /home/rvagg/node/benchmark/es/map-bench.js:49:20

…

 [C++]:
   ticks  total  nonlib   name
    276 9.8%    9.9%  v8::internal::NameDictionaryBase<v8::internal::NameDictionary, v8::internal::NameDictionaryShape>::FindEntry(v8::internal::Handle<v8::internal::Name>)
    239 8.5%    8.5%  v8::internal::StringTable::LookupKey(v8::internal::Isolate*, v8::internal::HashTableKey*)
    127 4.5%    4.5%  v8::internal::HashTable<v8::internal::NameDictionary, v8::internal::NameDictionaryShape, v8::internal::Handle<v8::internal::Name> >::Rehash(v8::internal::Handle<v8::int

…

 [Summary]:
   ticks  total  nonlib   name
    372   13.2%   13.3%  JavaScript
   2223   78.9%   79.5%  C++
     43  1.5%    1.5%  GC
     23  0.8%        Shared libraries
    201 7.1%        Unaccounted

…

(输出已经做了大量修剪,只保留有用的)

你不仅可以用这个输出查看哪一部分的代码占用虚拟机时间最多,知道它们被调用的地方,还可以直观的了解到虚拟机是如何对待你的代码的。举个例子,JavaScript函数名旁边的 * 表明这个代码被 V8引擎优化了。更多的关于如何去读取这个数据可以在 V8 wiki 中找到。

当然,如果你想在 生产环境运行时,进行分析,你可以尝试 NodeSource 的 N|Solid 来以图形格式查看类似数据。

10. 进程 CPU 使用情况

这个 process.cpuUsage() API 是在 v6.1.0 时加入 Node 核心的。返回一个包含用户和系统当前微秒下进程的 CPU 时间。

这个API允许通过检查API的两次调用之间的总时间差,以及一些额外的分支来推断CPU处于活动状态的时间。

相关文章