寫 Node-js 代碼,從學會調試開始
在紛繁複雜的代碼世界中,出錯是難免的,也許在傳統的前端代碼中,你習慣於 console 來排查問題,這是不合理的,在現代的社會下,調試代碼是你最快找到問題的方法。
這篇文章就是教你如何快速的使用調試找到問題。查找和識別錯誤的速度越快,你下班的時間就越早:)。
在當前 Node.js v15 版本下,以前非常多的調試方式已經失效了,Node.js 傳統的調試協議也進行了許多升級,我們按照最新的方式,來告訴你如何調試。
爲什麼要使用調試
衆所周知,代碼是寫(調)出來的,而不是猜出來的。
如果不通過調試運行代碼,那麼意味着需要去猜測代碼中發生的事情,YY 一下,如果代碼運行到這個地方,這個值可能是什麼。使用調試的主要好處就是可以觀察程序的運行情況,而不用做假設,可以一次跟隨程序執行一行代碼。
另一方面,你可以控制代碼執行的邏輯,你可以暫定執行,或者逐行運行,甚至修改內存中的值,讓它走到另一個分支裏。
Node.js 內置的調試
使用 Node.js 內置的調試方式是最簡單直接的,但是現階段都有 IDE,所以大家都不太關心底層的實現,一鍵開啓調試就行了。
而實際上 IDE 的調試都是基於這個內置調試之上的。
在瞭解內置的 Node.js 調試方式之前,我們先來了解一下另一個概念:斷點(breakpoint)。
斷點
顧名思義,斷點就是能斷住代碼執行的點,一般情況下,它的表現真的是個點。
比如 vscode 裏的斷點(紅紅的點,十分醒目)。
image.png
斷點會強制任何 JavaScript 調試器在給定點暫停。這樣就可以讓代碼執行到這個地方停下,觀察這行代碼以及之後代碼裏的變量值。
讓我們迴歸傳統,在沒有 IDE 的情況下(比如文本編輯器,Vim 啥的),都是使用 debugger
語句來讓打斷點的。
您使用調試器語句。您可以在代碼的任何位置添加此語句,比如:
asyncfunction initMethod() {
debugger;
console.log('bbb');
}
initMethod();
這樣,我們就希望調試的時候會在這一行停下來。
調試模式
光有斷點還不行,普通情況下,Node.js 會忽略這個 debugger,只有開了調試模式纔會暫停到這一行(原因是調試器太強大,有些惡意行爲可以通過它注入代碼)。
通過給 node 增加 --inspect
參數纔會開啓調試模式,這個模式下,還會開放一個默認的 9229 端口,允許其他 IDE 接入。
這個模式下,會輸出下面的信息:
Debugger listening on ws://127.0.0.1:9229/d598ab05-88e8-433f-b641-bf2766da97f5
For help, see: https://nodejs.org/en/docs/inspector
ws://127.0.0.1:9229/d598ab05-88e8-433f-b641-bf2766da97f5
是暴露的調試鏈接,裏面包含了協議,host,端口和一個唯一的 uuid。這是一個標準 v8 調試協議。
我們執行一下這個命令。
咦,爲啥什麼反應都沒有,代碼直接執行結束了,腦中一個大大問號?
事實上,僅僅開啓調試還是不夠的,調試器還沒有接收到足夠的信息,或者說沒有一個展現調試的地方。
node 還提供了另一個會卡住的調試命令。--inspect-brk
會停在代碼的第一行,等待下一步的指示,用他就行了。
有許多種方法可以作爲 UI,而最簡單的就是我們電腦上一般都會有的 Chrome 瀏覽器。
Chrome 自帶了一個調試頁 chrome://inspect/
,打開後,如果是在本機,會直接列出可調式的端口和文件地址(如果在遠程,也可以配置 ip)。
點擊這個 inspect
,添加我們的項目後,藍色的斷點條就乖乖的展現到眼前了。
在 Chrome UI 打開的時候,控制檯會輸出一句話。
表明這個調試協議已經連上了 node 開啓的調試端口。
我們總結一下,整個調試分爲兩個部分,“開啓 node 調試端口” + “符合 v8 調試協議的調試器 attach 到調試端口”。
VSCode 調試
VSCode 是我們最常用的 IDE,集成了調試的 UI,所以我們不再需要開啓 Chrome 來調試了。
本質和最基本的一樣,開啓調試端口,連接調試端口。只是 VSCode 本身是個編輯器,可以直接在其之上打斷點,集成度更高,這也是爲什麼我們一般都使用 IDE 的緣故。
VSCode 提供了一個調試 UI,需要用戶配置一個 launch.json(等價於啓動命令)。
image.png
內容如下,核心是 runtimeExecutable
使用的命令,以及 runtimeArgs
參數,這裏不再需要 --inspect
了(IDE 內部會處理)。
{
// 使用 IntelliSense 瞭解相關屬性。
// 懸停以查看現有屬性的描述。
// 欲瞭解更多信息,請訪問: https://go.microsoft.com/fwlink/?linkid=830387
"version": "0.2.0",
"configurations": [{
"name": "test",
"type": "node",
"request": "launch",
"cwd": "${workspaceRoot}",
"runtimeExecutable": "node",
"runtimeArgs": [
"test.js"
],
"console": "integratedTerminal",
"protocol": "auto",
"restart": true,
"port": 7001,
"autoAttachChildProcesses": true
}]
}
在上面的配置字段中有個 request
字段,有兩個值可以選擇:launch
和 attach
, 它表示 VS Code 中核心的兩種調試模式。
launch 指的是直接由編輯器啓動(直接 fork 一個進程),比如我們這個示例,而 attach 表示服務已經啓動,我們是 attach 到原來那個進程中,比如上面的 Chrome 調試。_然後打上斷點,執行就行了。
執行的時候,我們發現命令行會發現一段話。
cd /Users/harry/project/application/my_midway_app ; /usr/bin/env 'NODE_OPTIONS=--require "/Applications/Visual Studio Code.app/Contents/Resources/app/extensions/ms-vscode.js-debug/src/bootloader.bundle.js" --inspect-publish-uid=http' 'VSCODE_INSPECTOR_OPTIONS={"inspectorIpc":"/var/folders/xw/yl56_kmj5nd_r0cql7rcv8640000gn/T/node-cdp.94650-2.sock","deferredMode":false,"waitForDebugger":"","execPath":"/Users/harry/.nvs/default/bin/node","onlyEntrypoint":false,"autoAttachMode":"always","fileCallback":"/var/folders/xw/yl56_kmj5nd_r0cql7rcv8640000gn/T/node-debug-callback-02a1ac2abe751152"}' /Users/harry/.nvs/default/bin/node test.js
第一個 cd 忽略,我們主要看看中間這段。VSCode 啓動的時候加載 bootloader.bundle.js
這個文件,然後傳了一堆 IPC 啓動參數,比如創建了一個 sock 文件,其餘的把 launch 裏的參數翻譯了一下傳入。
核心就是這個 bootlaoder
文件,由於 VSCode 是 ts 寫的,這個文件的源碼在這。
https://github.com/microsoft/vscode-js-debug/blob/ca280351b2/src/targets/node/bootloader.ts
最核心的代碼是 inspectOrQueue
方法,代碼如下,其中有幾個特別關鍵的地方。
function inspectOrQueue(env: IBootloaderInfo): boolean {
// 省略
// 如果沒有傳 --inspect,則開啓調試端口
const openedFromCli = inspector.url() !== undefined;
if (!openedFromCli) {
// if the debugger isn't explicitly enabled, turn it on based on our inspect mode
if (!shouldForceProcessIntoDebugMode(env)) {
returnfalse;
}
inspector.open(0, undefined, false);
}
const info: IAutoAttachInfo = {
ipcAddress: env.inspectorIpc || '',
pid: String(process.pid),
telemetry,
scriptName: process.argv[1],
inspectorURL: inspector.url() asstring,
waitForDebugger: true,
ppid: String(env.ppid ?? ''),
};
if (mode === Mode.Immediate) {
// 同步模式,直接跟着應用啓動,監聽調試端口
spawnWatchdog(env.execPath || process.execPath, info);
} else {
// 異步模式,等進程啓動,attach 監聽端口
const { status, stderr } = spawnSync(
env.execPath || process.execPath,
[
'-e',
`const c=require("net").createConnection(process.env.NODE_INSPECTOR_IPC);setTimeout(()=>{console.error("timeout"),process.exit(1)},10000),c.on("error",e=>{console.error(e),process.exit(1)}),c.on("connect",()=>{c.write(process.env.NODE_INSPECTOR_INFO,"utf-8"),c.write(Buffer.from([0])),c.on("data",e=>{console.error("read byte",e[0]),process.exit(e[0])})});`,
],
{
env: {
NODE_SKIP_PLATFORM_CHECK: process.env.NODE_SKIP_PLATFORM_CHECK,
NODE_INSPECTOR_INFO: JSON.stringify(info),
NODE_INSPECTOR_IPC: env.inspectorIpc,
},
},
);
}
// 省略
returntrue;
}
不管是異步還是同步的模式,其原理都是 Node.js 最基礎的 “開啓端口”,“連接調試端口” 這兩個步驟。VSCode 還會考慮到別的場景,比如代碼創建子進程時,會將子進程也自動添加調試參數,方便自動 attach 等。
在這裏,我們會發現一個新的名詞,叫 AutoAttach
。這是 VSCode 在 2018 年 7 月提出的新名詞,微軟表示用戶基本都不太會寫 launch.json 文件,經常寫錯(沒錯,就是我),所以爲了簡化寫法,特地做的新功能。
這個功能怎麼用呢?
簡單的來說,只要啓動的 node 加上 --inspect
命令,VSCode 就能自動監視到,並且 attach 到進程裏開啓調試,不再需要複雜的配置。
有幾種附加方式。
這樣我們只要在 VSCode 終端裏輸入任意帶有 --inspect
的命令,就會自動被斷點到了,很香。
總結一下
調試到這裏基本就講完了,所有的調試的原理都是一樣的,藉由 Node.js 原生的打開調試端口的能力,不同的 IDE 才能連接到該端口,進而做出更加強大的能力。
比如 VSCode 不僅僅能做傳統的調試,也能增加配置,在執行調試前後增加鉤子,執行自己的命令,這都是擴展能力的體現。
相信你看完這篇文章,對 Node.js 應用的調試方式有了一定的理解,寫出更好的代碼。
轉自:公衆號( Node 地下鐵 ) - 張挺
本文由 Readfog 進行 AMP 轉碼,版權歸原作者所有。
來源:https://mp.weixin.qq.com/s/0LLtnL-4R2B1loXhv7Qrpw