歡迎關注Github倉庫,這是一個自2018年起持續更新的前端&算法開源博客。目前已有node學習、js面試筆記、css3動畫設計、webpack4系列教程、設計模式、劍指offer·js版等多個系列。倉庫地址:https://github.com/dongyuanxin/blogjavascript
process 模塊是 nodejs 提供給開發者用來和當前進程交互的工具,它的提供了不少實用的 API。從文檔出發,管中窺豹,進一步認識和學習 process 模塊:css
命令行參數指的是 2 個方面:html
node --harmony script.js --version
中,--harmony
就是傳給 node 的參數node script.js --version --help
中,--version --help
就是傳給進程的參數它們分別經過 process.argv
和 process.execArgv
來得到。前端
經過process.cwd()
能夠獲取當前的工做目錄。java
經過process.chdir(directory)
能夠切換當前的工做目錄,失敗後會拋出異常。實踐以下:node
function safeChdir(dir) { try { process.chdir(dir); return true; } catch (error) { return false; } }
Nodejs 能夠經過 try-catch 來捕獲異常。若是異常未捕獲,則會一直從底向事件循環冒泡。如是冒泡到事件循環的異常沒被處理,那麼就會致使當前進程異常退出。webpack
根據文檔,能夠經過監聽 process 的 uncaughtException 事件,來處理未捕獲的異常:css3
process.on("uncaughtException", (err, origin) => { console.log(err.message); }); const a = 1 / b; console.log("abc"); // 不會執行
上面的代碼,控制檯的輸出是:b is not defined
。捕獲了錯誤信息,而且進程以0
退出。開發者能夠在 uncaughtException 事件中,清除一些已經分配的資源(文件描述符、句柄等),不推薦在其中重啓進程。git
若是一個 Promise 回調的異常沒有被.catch()
捕獲,那麼就會觸發 process 的 unhandledRejection 事件:github
process.on("unhandledRejection", (err, promise) => { console.log(err.message); }); Promise.reject(new Error("錯誤信息")); // 未被catch捕獲的異常,交由unhandledRejection事件處理
告警不是 Node.js 和 Javascript 錯誤處理流程的正式組成部分。 一旦探測到可能致使應用性能問題,缺陷或安全隱患相關的代碼實踐,Node.js 就可發出告警。
好比前一段代碼中,若是出現未被捕獲的 promise 回調的異常,那麼就會觸發 warning 事件。
一個 nodejs 進程,能夠經過 process.exit() 來指定退出代碼,直接退出。不推薦直接使用 process.exit(),這會致使事件循環中的任務直接不被處理,以及可能致使數據的截斷和丟失(例如 stdout 的寫入)。
setTimeout(() => { console.log("我不會執行"); }); process.exit(0);
正確安全的處理是,設置 process.exitCode,並容許進程天然退出。
setTimeout(() => { console.log("我不會執行"); }); process.exitCode = 1;
用於處理進程退出的事件有: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' 事件監聽器以後,Node.js 進程將當即退出,從而致使在事件循環中仍排隊的任何其餘工做被放棄。
process 提供了 3 個標準流。須要注意的是,它們有些在某些時候是同步阻塞的(請見文檔)。
console.error
的底層實現,默認對應屏幕console.log
的底層實現,默認對應屏幕下面是基於「生產者-消費者模型」的讀取控制檯輸入而且及時輸出的代碼:
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 的時候是比較懵的,看文檔能夠知道,它的用途是:把回調函數做爲微任務,放入事件循環的任務隊列中。但這麼作的意義是什麼呢?
由於 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,前者效率更高,而且嚴格來講,二者建立的事件在任務隊列中順序並不同(請看前面的代碼)。
掌握 nodejs 的 child_process 模塊可以極大提升 nodejs 的開發能力,例如主從進程來優化 CPU 計算的問題,多進程開發等等。本文從如下幾個方面介紹 child_process 模塊的使用:
nodejs 的 child_process 模塊建立子進程的方法:spawn, fork, 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("我是子進程");
運行後結果:
父進程收到消息: 我是子進程 子進程收到消息: 我是父進程
在正常狀況下,父進程必定會等待子進程退出後,才退出。若是想讓父進程先退出,不受到子進程的影響,那麼應該:
unref()
options.detached
設置爲 truemain.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}`); } });