共计 5850 个字符,预计需要花费 15 分钟才能阅读完成。
过程:process 模块
process 模块是 nodejs 提供给开发者用来和以后过程交互的工具,它的提供了很多实用的 API。从文档登程,管中窥豹,进一步意识和学习 process 模块:
- 如何解决命令参数?
- 如何解决工作目录?
- 如何解决异样?
- 如何解决过程退出?
- process 的规范流对象
- 深刻了解 process.nextTick
如何解决命令参数?
命令行参数指的是 2 个方面:
- 传给 node 的参数。例如
node --harmony script.js --version
中,--harmony
就是传给 node 的参数 - 传给过程的参数。例如
node script.js --version --help
中,--version --help
就是传给过程的参数
它们别离通过 process.argv
和 process.execArgv
来取得。
如何解决工作目录?
通过 process.cwd()
能够获取以后的工作目录。
通过 process.chdir(directory)
能够切换以后的工作目录,失败后会抛出异样。实际如下:
function safeChdir(dir) {
try {process.chdir(dir);
return true;
} catch (error) {return false;}
}
如何解决异样?
uncaughtException 事件
Nodejs 能够通过 try-catch 来捕捉异样。如果异样未捕捉,则会始终从底向事件循环冒泡。如是冒泡到事件循环的异样没被解决,那么就会导致以后过程异样退出。
依据文档,能够通过监听 process 的 uncaughtException 事件,来解决未捕捉的异样:
process.on("uncaughtException", (err, origin) => {console.log(err.message);
});
const a = 1 / b;
console.log("abc"); // 不会执行
下面的代码,控制台的输入是:b is not defined
。捕捉了错误信息,并且过程以 0
退出。开发者能够在 uncaughtException 事件中,革除一些曾经调配的资源(文件描述符、句柄等),不举荐在其中重启过程。
unhandledRejection 事件
如果一个 Promise 回调的异样没有被 .catch()
捕捉,那么就会触发 process 的 unhandledRejection 事件:
process.on("unhandledRejection", (err, promise) => {console.log(err.message);
});
Promise.reject(new Error("错误信息")); // 未被 catch 捕捉的异样,交由 unhandledRejection 事件处理
warning 事件
告警不是 Node.js 和 Javascript 错误处理流程的正式组成部分。一旦探测到可能导致利用性能问题,缺点或安全隐患相干的代码实际,Node.js 就可收回告警。
比方前一段代码中,如果呈现未被捕捉的 promise 回调的异样,那么就会触发 warning 事件。参考 nodejs 进阶视频解说:进入学习
如何解决过程退出?
process.exit() vs process.exitCode
一个 nodejs 过程,能够通过 process.exit() 来指定退出代码,间接退出。不举荐间接应用 process.exit(),这会导致事件循环中的工作间接不被解决,以及可能导致数据的截断和失落(例如 stdout 的写入)。
setTimeout(() => {console.log("我不会执行");
});
process.exit(0);
正确平安的解决是,设置 process.exitCode,并容许过程天然退出。
setTimeout(() => {console.log("我不会执行");
});
process.exitCode = 1;
beforeExit 事件
用于解决过程退出的事件有:beforeExit 事件 和 exit 事件。
当 Node.js 清空其事件循环并且没有其余工作要安顿时,会触发 beforeExit 事件。例如在退出前须要一些异步操作,那么能够写在 beforeExit 事件中:
let hasSend = false;
process.on("beforeExit", () => {if (hasSend) return; // 防止死循环
setTimeout(() => {console.log("mock send data to serve");
hasSend = true;
}, 500);
});
console.log(".......");
// 输入:// .......
// mock send data to serve
留神:在 beforeExit 事件中如果是异步工作,那么又会被增加到工作队列。此时,工作队列实现所有工作后,又回触发 beforeExit 事件。因而,不解决的话,可能呈现死循环的状况。如果是显式调用 exit(),那么不会触发此事件。
exit 事件
在 exit 事件中,只能执行同步操作。在调用 ‘exit’ 事件监听器之后,Node.js 过程将立刻退出,从而导致在事件循环中仍排队的任何其余工作被放弃。
process 的规范流对象
process 提供了 3 个规范流。须要留神的是,它们有些在某些时候是同步阻塞的(请见文档)。
- process.stderr:WriteStream 类型,
console.error
的底层实现,默认对应屏幕 - process.stdout:WriteStream 类型,
console.log
的底层实现,默认对应屏幕 - process.stdin:ReadStream 类型,默认对应键盘输入
上面是基于“生产者 - 消费者模型”的读取控制台输出并且及时输入的代码:
process.stdin.setEncoding("utf8");
process.stdin.on("readable", () => {
let chunk;
while ((chunk = process.stdin.read()) !== null) {process.stdout.write(`>>> ${chunk}`);
}
});
process.stdin.on("end", () => {process.stdout.write("完结");
});
对于事件的含意,还是请看 stream 的文档。
深刻了解 process.nextTick
我第一次看到 process.nextTick 的时候是比拟懵的,看文档能够晓得,它的用处是:把回调函数作为微工作,放入事件循环的工作队列中。但这么做的意义是什么呢?
因为 nodejs 并不适宜计算密集型的利用,一个过程就一个线程,在当下工夫点上,就一个事件在执行。那么,如果咱们的事件占用了很多 cpu 工夫,那么之后的事件就要期待十分久。所以,nodejs 的一个编程准则是尽量缩短每一个事件的执行事件 。process.nextTick 的作用就在这, 将一个大的工作分解成多个小的工作。示例代码如下:
// 被拆分成 2 个函数执行
function BigThing() {doPartThing();
process.nextTick(() => finishThing());
}
在事件循环中,何时执行 nextTick 注册的工作呢?请看上面的代码:
setTimeout(function() {console.log("第一个 1 秒");
process.nextTick(function() {console.log("第一个 1 秒:nextTick");
});
}, 1000);
setTimeout(function() {console.log("第 2 个 1 秒");
}, 1000);
console.log("我要输入 1");
process.nextTick(function() {console.log("nextTick");
});
console.log("我要输入 2");
输入的后果如下,nextTick 是早于 setTimeout:
我要输入 1
我要输入 2
nextTick
第一个 1 秒
第一个 1 秒:nextTick
第 2 个 1 秒
在浏览器端,nextTick 会进化成 setTimeout(callback, 0)
。但在 nodejs 中请应用 nextTick 而不是 setTimeout,前者效率更高,并且严格来说,两者创立的事件在工作队列中程序并不一样(请看后面的代码)。
子过程:child_process 模块
把握 nodejs 的 child_process 模块可能极大进步 nodejs 的开发能力,例如主从过程来优化 CPU 计算的问题,多过程开发等等。本文从以下几个方面介绍 child_process 模块的应用:
- 创立子过程
- 父子过程通信
- 独立子过程
- 过程管道
创立子过程
nodejs 的 child_process 模块创立子过程的办法:spawn, fork, exec, execFile。它们的关系如下:
- fork, exec, execFile 都是通过 spawn 来实现的。
- exec 默认会创立 shell。execFile 默认不会创立 shell,意味着不能应用 I/O 重定向、file glob,但效率更高。
- spawn、exec、execFile 都有同步版本,可能会造成过程阻塞。
child_process.spawn()
的应用:
const {spawn} = require("child_process");
// 返回 ChildProcess 对象,默认状况下其上的 stdio 不为 null
const ls = spawn("ls", ["-lh"]);
ls.stdout.on("data", data => {console.log(`stdout: ${data}`);
});
ls.stderr.on("data", data => {console.error(`stderr: ${data}`);
});
ls.on("close", code => {console.log(` 子过程退出,退出码 ${code}`);
});
child_process.exec()
的应用:
const {exec} = require("child_process");
// 通过回调函数来操作 stdio
exec("ls -lh", (err, stdout, stderr) => {if (err) {console.error(` 执行的谬误: ${err}`);
return;
}
console.log(`stdout: ${stdout}`);
console.error(`stderr: ${stderr}`);
});
父子过程通信
fork()
返回的 ChildProcess 对象,监听其上的 message 事件,来承受子过程音讯;调用 send 办法,来实现 IPC。
parent.js 代码如下:
const {fork} = require("child_process");
const cp = fork("./sub.js");
cp.on("message", msg => {console.log("父过程收到音讯:", msg);
});
cp.send("我是父过程");
sub.js 代码如下:
process.on("message", m => {console.log("子过程收到音讯:", m);
});
process.send("我是子过程");
运行后后果:
父过程收到音讯:我是子过程
子过程收到音讯:我是父过程
独立子过程
在失常状况下,父过程肯定会期待子过程退出后,才退出。如果想让父过程先退出,不受到子过程的影响,那么应该:
- 调用 ChildProcess 对象上的
unref()
options.detached
设置为 true- 子过程的 stdio 不能是连贯到父过程
main.js 代码如下:
const {spawn} = require("child_process");
const subprocess = spawn(process.argv0, ["sub.js"], {
detached: true,
stdio: "ignore"
});
subprocess.unref();
sub.js 代码如下:
setInterval(() => {}, 1000);
过程管道
options.stdio 选项用于配置在父过程和子过程之间建设的管道。默认状况下,子过程的 stdin、stdout 和 stderr 会被重定向到 ChildProcess 对象上相应的 subprocess.stdin、subprocess.stdout 和 subprocess.stderr 流。这意味着能够通过监听其上的 data
事件,在父过程中获取子过程的 I/O。
能够用来实现“重定向”:
const fs = require("fs");
const child_process = require("child_process");
const subprocess = child_process.spawn("ls", {
stdio: [
0, // 应用父过程的 stdin 用于子过程。"pipe", // 把子过程的 stdout 通过管道传到父过程。fs.openSync("err.out", "w") // 把子过程的 stderr 定向到一个文件。]
});
也能够用来实现 ” 管道运算符 ”:
const {spawn} = require("child_process");
const ps = spawn("ps", ["ax"]);
const grep = spawn("grep", ["ssh"]);
ps.stdout.on("data", data => {grep.stdin.write(data);
});
ps.stderr.on("data", err => {console.error(`ps stderr: ${err}`);
});
ps.on("close", code => {if (code !== 0) {console.log(`ps 过程退出,退出码 ${code}`);
}
grep.stdin.end();});
grep.stdout.on("data", data => {console.log(data.toString());
});
grep.stderr.on("data", data => {console.error(`grep stderr: ${data}`);
});
grep.on("close", code => {if (code !== 0) {console.log(`grep 过程退出,退出码 ${code}`);
}
});