大家好,我是若川(點這里加我微信?ruochuan12,長期交流學習)。今天推薦這篇調試文章,熟悉我的讀者都知道我寫的源碼文章都多次強調要調試,而且寫了調試方法。
點擊下方卡片關注我、加個星標,或者查看源碼等系列文章。學習源碼整體架構系列、年度總結、JS基礎系列
在紛繁復雜的代碼世界中,出錯是難免的,也許在傳統的前端代碼中,你習慣于 console 來排查問題,這是不合理的,在現代的社會下,調試代碼是你最快找到問題的方法。
這篇文章就是教你如何快速的使用調試找到問題。查找和識別錯誤的速度越快,你下班的時間就越早:)。
在當前 Node.js v15 版本下,以前非常多的調試方式已經失效了,Node.js 傳統的調試協議也進行了許多升級,我們按照最新的方式,來告訴你如何調試。
為什么要使用調試
眾所周知,代碼是寫(調)出來的,而不是猜出來的。
如果不通過調試運行代碼,那么意味著需要去猜測代碼中發生的事情,YY 一下,如果代碼運行到這個地方,這個值可能是什么。使用調試的主要好處就是可以觀察程序的運行情況,而不用做假設,可以一次跟隨程序執行一行代碼。
另一方面,你可以控制代碼執行的邏輯,你可以暫定執行,或者逐行運行,甚至修改內存中的值,讓它走到另一個分支里。
Node.js 內置的調試
使用 Node.js 內置的調試方式是最簡單直接的,但是現階段都有 IDE,所以大家都不太關心底層的實現,一鍵開啟調試就行了。
而實際上 IDE 的調試都是基于這個內置調試之上的。
在了解內置的 Node.js 調試方式之前,我們先來了解一下另一個概念:斷點(breakpoint)。
斷點
顧名思義,斷點就是能斷住代碼執行的點,一般情況下,它的表現真的是個點。
比如 vscode 里的斷點(紅紅的點,十分醒目)。

斷點會強制任何 JavaScript 調試器在給定點暫停。這樣就可以讓代碼執行到這個地方停下,觀察這行代碼以及之后代碼里的變量值。
讓我們回歸傳統,在沒有 IDE 的情況下(比如文本編輯器,Vim 啥的),都是使用 debugger
?語句來讓打斷點的。
您使用調試器語句。您可以在代碼的任何位置添加此語句,比如:
async function 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
?會停在代碼的第一行,等待下一步的指示,用他就行了。但是這只是普通的卡住代碼,我們需要能支持 v8調試協議的 UI。
有許多種方法可以作為 UI,而最簡單的就是我們電腦上一般都會有的 Chrome 瀏覽器。
Chrome 自帶了一個調試頁 chrome://inspect/
?,打開后,如果是在本機,會直接列出可調式的端口和文件地址(如果在遠程,也可以配置 ip)。
點擊這個 inspect
?,添加我們的項目后,藍色的斷點條就乖乖的展現到眼前了。這個時候,我們就可以進行單步調試了(不需要 debugger 了)。
在 Chrome UI 打開的時候,控制臺會輸出一句話。
表明這個調試協議已經連上了 node 開啟的調試端口。
我們總結一下,整個調試分為兩個部分,“開啟 node 調試端口” + “符合 v8調試協議的調試器 attach 到調試端口”。
VSCode 調試
VSCode 是我們最常用的 IDE,集成了調試的 UI,所以我們不再需要開啟 Chrome 來調試了。
本質和最基本的一樣,開啟調試端口,連接調試端口。只是 VSCode 本身是個編輯器,可以直接在其之上打斷點,集成度更高,這也是為什么我們一般都使用 IDE 的緣故。
VSCode 提供了一個調試 UI,需要用戶配置一個 launch.json(等價于啟動命令)。

內容如下,核心是 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 modeif (!shouldForceProcessIntoDebugMode(env)) {return false;}inspector.open(0, undefined, false); }const info: IAutoAttachInfo = {ipcAddress: env.inspectorIpc || '',pid: String(process.pid),telemetry,scriptName: process.argv[1],inspectorURL: inspector.url() as string,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,},},);}// 省略return true;
}
不管是異步還是同步的模式,其原理都是 Node.js 最基礎的 “開啟端口”,“連接調試端口” 這兩個步驟。VSCode 還會考慮到別的場景,比如代碼創建子進程時,會將子進程也自動添加調試參數,方便自動 attach 等。
在這里,我們會發現一個新的名詞,叫 AutoAttach
?。這是 VSCode 在 2018 年 7 月提出的新名詞,微軟表示用戶基本都不太會寫 launch.json 文件,經常寫錯(沒錯,就是我),所以為了簡化寫法,特地做的新功能。
這個功能怎么用呢?
簡單的來說,只要啟動的 node 加上 --inspect
?命令,VSCode 就能自動監視到,并且 attach 到進程里開啟調試,不再需要復雜的配置。開啟的命令加到了選項里(cmd+shift+p 搜索)。
有幾種附加方式。比較常用的是僅帶標志。
這樣我們只要在 VSCode 終端里輸入任意帶有 --inspect
?的命令,就會自動被斷點到了,很香。
總結一下
調試到這里基本就講完了,所有的調試的原理都是一樣的,藉由 Node.js 原生的打開調試端口的能力,不同的 IDE 才能連接到該端口,進而做出更加強大的能力。
比如 VSCode 不僅僅能做傳統的調試,也能增加配置,在執行調試前后增加鉤子,執行自己的命令,這都是擴展能力的體現。
相信你看完這篇文章,對 Node.js 應用的調試方式有了一定的理解,寫出更好的代碼。
最近組建了一個江西人的前端交流群,如果你也是江西人可以加我微信 ruochuan12 拉你進群。
·················?若川出品?·················
今日話題
2019年9月20日那時關注粉絲突破了1000,2020年佛系了很久,運營了快兩年,還沒到達一萬關注,間接說明了運營公眾號的難度。我的目標是早日突破一萬關注,目前進度9062/10000,如果你愿意幫忙宣傳我的公眾號助力早日突破五位數,那真是太好了。歡迎分享、收藏、點贊、在看我的公眾號文章~
一個愿景是幫助5年內前端人走向前列的公眾號
可加我個人微信 ruochuan12,長期交流學習
推薦閱讀
我在阿里招前端,我該怎么幫你?(現在還能加我進模擬面試群)
若川知乎問答:2年前端經驗,做的項目沒什么技術含量,怎么辦?
點擊上方卡片關注我、加個星標,或者查看源碼等系列文章。
學習源碼整體架構系列、年度總結、JS基礎系列