v11.10 nodejs 学习 (一)

学而时更之,不亦乐乎

版本:V11.10 LTS

Note: 内容主要以个人基础为起点,只记录了个人认为需要的东西。官网

Async Hooks

v8 新增。用于监测所有异步操作。目前处于实验阶段。

async_hooks.executionAsyncId()

返回当前环境上下文的 ID。

async_hooks.triggerAsyncId()

返回触发当前代码执行的环境上下文 ID。比如,在一个 Tick(环境 ID 假设为 1)中执行了异步操作 A,那么 A 的 triggerId 就是 1。

async_hooks.createHook(callbacks)

  • callback: <对象>

创建一个 Hook 实例 hook,callback 是个回调对象,可包含 init/before/after/destroy/promiseResolve。__NOTE__:由于 console.log 在 Nodejs 中也是异步操作,因此不能在各个回调中使用 log 来打印信息,会无限循环。常见的采用写文件方式。关于 console.log 是异步的验证,如下代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
const asyncHooks = require('async_hooks');
const fs = require('fs');
console.log(process.stdout.isTTY);

asyncHooks.createHook({
init(asyncId, type, triggerAsyncId, resource) {
const eid = asyncHooks.executionAsyncId();
debugger;
fs.writeSync(
1, `init: ${type}(${asyncId}): trigger: ${triggerAsyncId} execution: ${eid}\n`);
}
}).enable();

require('net').createServer((conn) => {}).listen(8080);
console.log('log1');
console.log('log2');

会输出:

1
2
3
4
5
6
7
true
init: TCPSERVERWRAP(5): trigger: 1 execution: 1
init: TickObject(6): trigger: 5 execution: 1
log1
init: TickObject(7): trigger: 1 execution: 1
log2
init: TickObject(8): trigger: 1 execution: 1

然后代码中去掉最后两行 log,输出:

1
2
3
true
init: TCPSERVERWRAP(5): trigger: 1 execution: 1
init: TickObject(6): trigger: 5 execution: 1

发现,console.log 的调用过程存在异步的调用(关于 process.stdout 是异步还是同步,process 一节有说明,但我觉得不能解释这个 console.log 是异步 TODO)

根据文档及我的理解,executionAsyncId 为 0,是 C/C++ 环境上的上下文;1,是主程序的环境(及全局);之后递增的,便是走 event loop 的异步环境。triggerAsyncId 是触发这次异步操作的 executionAsyncId。

hook.enable() / disable()

开启或者关闭(在 create 后默认是关闭的)。

回调配置

before(async)

  • asyncId: 数字

当异步操作初始化后或者完成时,将调用回调来通知用户。before 回调在所述回调执行前执行。before 回调可能被执行 0 次或 N 次。当异步操作被取消时,before 不会被调用;类似 TCP 服务器会调用 before 多次;像 fs.open 只会调用一次。

after(asyncId)

  • asyncId: 数字

当通知用户的回调执行完成后立即执行。

destroy(asyncId)

当与 asyncId 相关的资源销毁时(依赖于 gc)被调用。它也会被内嵌的 API __emitDestroy ()__调用。

promiseResolve(asyncId)

当 Promise 的 resolve 调用时被调用。


Buffer

Buffer 为 Nodejs 提供了对二进制文件的操作。

关于构造函数

由于安全、易用性等,现已废除 new Buffer 构造器。使用 Buffer.alloc/from/allocUnsafe 等代替。

--zero-fill-buffers

开启每次生成 buffer 都清空数据。

关于 Buffer.from 是 copy 还是 share

当 Buffer.from 传入一个 TypedArray 时,会复制该 typedArray 的 content;当传入 typedArray.buffer 属性时,会 share 该 buffer。

遍历

可以只用 for…of,buf.keys (), buf.values (), buf.entries ()

Class: Buffer

new Buffer

所有参数形式的构造函数均废弃。采用 Buffer.from 替代。

Class Method: Buffer.alloc(size[, fill[, encoding]])

  • size: buffer 大小
  • fill:string|buffer|integer,默认为 0
  • encoding:默认 utf8
    如果指定了 fill 和 encoding,会在初始化后调用 buf.fill (fill, encoding) 来重置内存。

Class Method: Buffer.allocUnsafe(size)

基本跟上面那个一样,但是不会用 buf.fill (fill, encoding) 来重置内存。Buffer 内部有个预分配的内部 Buffer 实例,用于快速分配新的 Buffer 实例,前提是通过 Buffer.allowUnsafe 来创建。当大小小于 4k 时,会使用这个 pool。

Class Method: Buffer.allocUnsafeSlow(size)

不采用内部的 pool,可以长久保存(需要注意内存泄露的问题)。


Child Processes

默认上,stdin/stdout/stderr 的管道建立在 Nodejs 进程和衍生进程间。这些管道都是有限制的(通常还与平台有关),当子进程输出到 stdout 上超过了被消费的内容,子进程就会阻塞管道缓冲区接收更多数据。这与 shell 的管道行为一致。如果不消费输入,可以设置 **{stdio: ‘ignore’}**。

child_process.spawn () 会异步的产生子进程,不会阻塞 event loop;child_process.spawnSync () 同步的产生子进程,阻塞,直到进程退出或者被干掉。

Nodejs 提供了为数不多的同步和异步方法来代替 spawn/spawnSync。但是,其他 API 都是以 spawn/spawnSync 为基础实现的。

  • child_process.exec: 产生一个 shell,并在这个 shell 中运行命令,当结束后将 stdout 和 stderr 传给回调函数。
  • child_process.execFile: 与上者类似,但它直接执行命令而不是先生成一个 shell。
  • child_process.fork: 产生一个新的 Nodejs 进程,调用指定的模块建立 IPC 通信通道,允许在父和子之间发送消息。
  • child_process.execSync: 同步
  • child_process.execFileSync: 同步

虽然同步看着很棒,但会极大地影响 event loop 的速度。

所有异步的 API,都会返回一个 ChildProcess 实例,该类实现了 EventEmitter,所以只需对实例监听事件即可。exec 和 execFile 额外支持一个可选的 callback 参数,当子进程终结时会回调。

windows 上衍生.bat .cmd 文件

在类 Unix 平台(Unix、Linux、MacOS 等)上,execFile 更高效,因为它不会产生一个 shell。在 windows 上,.bat 和.cmd 文件不能在没有终端的情况下靠它自己运行起来,因此 windows 上不能调用 exeFile。在 Windows 上需要使用 exec 或者 spawn(例子见官网)。

exec(command[, options][, callback])

  • command: 字符串。参数以空格区分。
  • options: 对象
    • cwd: 工作目录。default: null
    • env: 环境键值对。default: null
    • encoding: 默认’utf8’
    • shell: 字符串,执行命令的 shell。UNIX 上默认’/bin/sh’,windows: process.env.ComSpec
    • timeout: 超时时间,默认 0
    • maxBuffer: 在 stdout 或 stderr 上最大的数据量(字节),如果超出,子进程会被干掉,输出也会被截断。
    • killSignal: 字符串或者数字。默认’SIGTERM’
    • uid: 数字
    • gid: 数字
    • windowsHide: 布尔。隐藏 windows 子进程的输出框。默认:false
  • callback: 当进城拉闸时调用
    • error
    • stdout
    • stderr
  • Returns: ChildProcess 实例

在生成的 shell 中运行 command 命令,传递给 exec 的命令由 shell 直接处理,以及一些特殊字符需要相应处理。

1
2
exec('"/path/to/test file/test.sh" arg1 arg2');
// path中包含空格的,需要包起来

如果传递了 callback 参数,它在子进程结束时会调用被(error, stdout, stderr)。成功时,error 为 null;有错误时,error 为 Error 实例。error.code 为退出状态码,error.signal 为被干掉时的信号。stdout/stderr 可能为字符串或者 buffer,取决于 options 中 encoding 的值。

如果运行超过 timeout,父进程会发送 SIGTERM(默认终结信号,可通过 killSignal 改变)来终结子进程。

child_process.execFile(file[, args][, options][, callback])

  • file: path to file
  • args: <string[]>

execFile 和 exec 很类似,但不会产生一个 shell。也因为如此,IO 的重定向、文件通配符等不支持。

child_process.fork(modulePath[, args][, options])

  • modulePath: 子进程中需要运行的模块的路径
  • args: <string[]>
  • options:
    • cwd
    • detached: 布尔。子进程独立于父进程运行。详见下文。
    • env
    • execPath: 用于创建子进程的可执行文件路径
    • execArgv: 字符串数组。default:process.execArgv
    • silent: 布尔。如果 true,子进程的 stdin、stdout、stderr 都会 pipe 到父进程,否则会继承父进程。
    • stdio: 当传递了该项,会覆盖 silent 选项。见下文
    • windowsVerbatimArguments
    • uid
    • gid
  • Returns: ChildProcess 实例

fork 的子进程实例有一些父子间进程内建的通信信道。fork 默认会使用父进程的 process.execPath 来产生新的 Node.js 实例。

child_process.spawn(command[, args][, options])

  • command: 要执行的命令
  • args: 字符串数组,
  • options:
    • cwd:
    • env
    • argv0: 设置 argv [0]。
    • stdio:见下文
    • detached
    • uid
    • gid
    • shell
    • windowsVerbatimArguments
    • windowsHide
  • Returns: ChildProcess 实例

options.detached

在 windows 上,设为 true,能够让子进程在父进程拉闸后继续运行。子进程有它自己的 console 窗口。一旦对一个子进程开启分离选项,就不能再关闭了。

在非 Windows 平台上,如果设为 true,子进程会成为一个新的进程组和会话的先导者。注意,父进程退出后,子进程可能会继续运行,而不管它们是否已分离。

默认情况下,父进程会等待分离的子进程退出。为了避免父进程等待子进程退出这种情况,可以在父进程中使用 subProcess.unref () (subProcess 是 ChildProcess 实例) 方法。原理是这样会让父进程的 event loop 排除子进程。能让父进程独立于子进程退出, 前提是父子进程间没有建立的 IPC 信道

当使用这个选项来开启长期运行的进程,当父进程退出后,子进程不会保持在后台继续运行,除非提供了未连接到父进程的 stdio 配置。如果继承了父进程的 stdio,子进程将保持链接到控制终端。

options.stdio

该选项用于配置建立在父子进程间的管道。默认情况下,子进程的 stdin、stdout、stderr 都重定向到相应的 subprocess.stdin、stdout、stderr 流上。这等于设置 options.stdio 为 [‘pipe’, ‘pipe’, ‘pipe’]。

stdio 可能为以下值:

  • ‘pipe’: 等于 [‘pipe’, ‘pipe’, ‘pipe’],为默认值。
  • ‘ignore’: 等于 [‘ignore’, ‘ignore’, ‘ignore’]
  • ‘inherit’: 等于 [‘inherit’, ‘inherit’, ‘inherit’] 或者 [0, 1, 2]

否则 options.stdio 就是一个数组。在 0,1,2 位置上可以为以下值:

  1. ‘pipe’ - 在父子进程间建立一个管道。管道的父端作为子进程对象的属性 subprocess.stdio[fd] 暴露到父进程。fds0-2 分别代表着 subprocess.stdin/stdout/stderr。
  2. ‘ipc’ - 在父子进程间建立传递信息、文件描述符的 IPC 通道。一个 ChildProcess 实例最多有一个 IPCstdio 文件描述符。设置为此项,会开启 subprocess.send () 方法。如果子进程是 Node.js 进程,IPC 通道的存在将启用 process.send () 和 process.disconnect () 方法,在子进程中也有 disconnect、message 事件产生。
    不支持不使用 process.send () 来获取 IPC 通道 fd 或者对不是 Node.js 实例的子进程使用 IPC 通道。
  3. ‘ignore’ - 子进程会忽略 fd。由于 Node.js 会对子进程始终打开 fds [0-2],设为该值会让 Node.js 打开 /dev/null 并且链接到子进程 fd 上。
  4. ‘inherit’ - 传递到父进程,类似与父进程共用控制终端的输入、输出。
  5. <Stream> - 共享一个可读或可写的流,比如一个文本终端、文件、socket 或者一个到子进程的管道。
  6. 正整数 - 被作为 fd 解释。
  7. null, undefined - 使用默认值。在 0-2,使用’pipe’,3 及以上,使用’ignore’

需要注意的是,当 IPC 通道建立在父子进程间,并且子进程是 Node.js 进程,子进程以未引用的 IPC 通道启动,直到子进程对’disconnect’或者’message’事件注册了回调。这允许子进程在没有被开放的 IPC 通道保持开启时正常退出。