<kbd id="afajh"><form id="afajh"></form></kbd>
<strong id="afajh"><dl id="afajh"></dl></strong>
    <del id="afajh"><form id="afajh"></form></del>
        1. <th id="afajh"><progress id="afajh"></progress></th>
          <b id="afajh"><abbr id="afajh"></abbr></b>
          <th id="afajh"><progress id="afajh"></progress></th>

          寫(xiě) Node.js 代碼,從學(xué)會(huì)調(diào)試開(kāi)始

          共 6120字,需瀏覽 13分鐘

           ·

          2021-04-03 21:23

          在紛繁復(fù)雜的代碼世界中,出錯(cuò)是難免的,也許在傳統(tǒng)的前端代碼中,你習(xí)慣于 console 來(lái)排查問(wèn)題,這是不合理的,在現(xiàn)代的社會(huì)下,調(diào)試代碼是你最快找到問(wèn)題的方法。

          這篇文章就是教你如何快速的使用調(diào)試找到問(wèn)題。查找和識(shí)別錯(cuò)誤的速度越快,你下班的時(shí)間就越早:)。

          在當(dāng)前 Node.js v15 版本下,以前非常多的調(diào)試方式已經(jīng)失效了,Node.js 傳統(tǒng)的調(diào)試協(xié)議也進(jìn)行了許多升級(jí),我們按照最新的方式,來(lái)告訴你如何調(diào)試。

          為什么要使用調(diào)試

          眾所周知,代碼是寫(xiě)(調(diào))出來(lái)的,而不是猜出來(lái)的。

          如果不通過(guò)調(diào)試運(yùn)行代碼,那么意味著需要去猜測(cè)代碼中發(fā)生的事情,YY 一下,如果代碼運(yùn)行到這個(gè)地方,這個(gè)值可能是什么。使用調(diào)試的主要好處就是可以觀(guān)察程序的運(yùn)行情況,而不用做假設(shè),可以一次跟隨程序執(zhí)行一行代碼。

          另一方面,你可以控制代碼執(zhí)行的邏輯,你可以暫定執(zhí)行,或者逐行運(yùn)行,甚至修改內(nèi)存中的值,讓它走到另一個(gè)分支里。

          Node.js 內(nèi)置的調(diào)試

          使用 Node.js 內(nèi)置的調(diào)試方式是最簡(jiǎn)單直接的,但是現(xiàn)階段都有 IDE,所以大家都不太關(guān)心底層的實(shí)現(xiàn),一鍵開(kāi)啟調(diào)試就行了。

          而實(shí)際上 IDE 的調(diào)試都是基于這個(gè)內(nèi)置調(diào)試之上的。

          在了解內(nèi)置的 Node.js 調(diào)試方式之前,我們先來(lái)了解一下另一個(gè)概念:斷點(diǎn)(breakpoint)。

          斷點(diǎn)

          顧名思義,斷點(diǎn)就是能斷住代碼執(zhí)行的點(diǎn),一般情況下,它的表現(xiàn)真的是個(gè)點(diǎn)。

          比如 vscode 里的斷點(diǎn)(紅紅的點(diǎn),十分醒目)。

          image.png

          斷點(diǎn)會(huì)強(qiáng)制任何 JavaScript 調(diào)試器在給定點(diǎn)暫停。這樣就可以讓代碼執(zhí)行到這個(gè)地方停下,觀(guān)察這行代碼以及之后代碼里的變量值。

          讓我們回歸傳統(tǒng),在沒(méi)有 IDE 的情況下(比如文本編輯器,Vim 啥的),都是使用 debugger 語(yǔ)句來(lái)讓打斷點(diǎn)的。

          您使用調(diào)試器語(yǔ)句。您可以在代碼的任何位置添加此語(yǔ)句,比如:

          async function initMethod() {
          debugger;
          console.log('bbb');
          }

          initMethod();

          這樣,我們就希望調(diào)試的時(shí)候會(huì)在這一行停下來(lái)。

          調(diào)試模式

          光有斷點(diǎn)還不行,普通情況下,Node.js 會(huì)忽略這個(gè) debugger,只有開(kāi)了調(diào)試模式才會(huì)暫停到這一行(原因是調(diào)試器太強(qiáng)大,有些惡意行為可以通過(guò)它注入代碼)。

          通過(guò)給 node 增加 --inspect 參數(shù)才會(huì)開(kāi)啟調(diào)試模式,這個(gè)模式下,還會(huì)開(kāi)放一個(gè)默認(rèn)的 9229 端口,允許其他 IDE 接入。

          這個(gè)模式下,會(huì)輸出下面的信息:

          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 是暴露的調(diào)試鏈接,里面包含了協(xié)議,host,端口和一個(gè)唯一的 uuid。這是一個(gè)標(biāo)準(zhǔn) v8 調(diào)試協(xié)議。

          我們執(zhí)行一下這個(gè)命令。

          咦,為啥什么反應(yīng)都沒(méi)有,代碼直接執(zhí)行結(jié)束了,腦中一個(gè)大大問(wèn)號(hào)?

          事實(shí)上,僅僅開(kāi)啟調(diào)試還是不夠的,調(diào)試器還沒(méi)有接收到足夠的信息,或者說(shuō)沒(méi)有一個(gè)展現(xiàn)調(diào)試的地方。

          node 還提供了另一個(gè)會(huì)卡住的調(diào)試命令。--inspect-brk 會(huì)停在代碼的第一行,等待下一步的指示,用他就行了。但是這只是普通的卡住代碼,我們需要能支持 v8調(diào)試協(xié)議的 UI。

          有許多種方法可以作為 UI,而最簡(jiǎn)單的就是我們電腦上一般都會(huì)有的 Chrome 瀏覽器。

          Chrome 自帶了一個(gè)調(diào)試頁(yè) chrome://inspect/ ,打開(kāi)后,如果是在本機(jī),會(huì)直接列出可調(diào)式的端口和文件地址(如果在遠(yuǎn)程,也可以配置 ip)。

          點(diǎn)擊這個(gè) inspect ,添加我們的項(xiàng)目后,藍(lán)色的斷點(diǎn)條就乖乖的展現(xiàn)到眼前了。這個(gè)時(shí)候,我們就可以進(jìn)行單步調(diào)試了(不需要 debugger 了)。

          在 Chrome UI 打開(kāi)的時(shí)候,控制臺(tái)會(huì)輸出一句話(huà)。

          表明這個(gè)調(diào)試協(xié)議已經(jīng)連上了 node 開(kāi)啟的調(diào)試端口。

          我們總結(jié)一下,整個(gè)調(diào)試分為兩個(gè)部分,“開(kāi)啟 node 調(diào)試端口” + “符合 v8調(diào)試協(xié)議的調(diào)試器 attach 到調(diào)試端口”。

          VSCode 調(diào)試

          VSCode 是我們最常用的 IDE,集成了調(diào)試的 UI,所以我們不再需要開(kāi)啟 Chrome 來(lái)調(diào)試了。

          本質(zhì)和最基本的一樣,開(kāi)啟調(diào)試端口,連接調(diào)試端口。只是 VSCode 本身是個(gè)編輯器,可以直接在其之上打斷點(diǎn),集成度更高,這也是為什么我們一般都使用 IDE 的緣故。

          VSCode 提供了一個(gè)調(diào)試 UI,需要用戶(hù)配置一個(gè) launch.json(等價(jià)于啟動(dòng)命令)。

          image.png

          內(nèi)容如下,核心是 runtimeExecutable 使用的命令,以及 runtimeArgs 參數(shù),這里不再需要 --inspect 了(IDE內(nèi)部會(huì)處理)。

          {
          // 使用 IntelliSense 了解相關(guān)屬性。
          // 懸停以查看現(xiàn)有屬性的描述。
          // 欲了解更多信息,請(qǐng)?jiān)L問(wèn): 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
          }]
          }

          在上面的配置字段中有個(gè) request 字段,有兩個(gè)值可以選擇:launch 和 attach , 它表示VS Code中核心的兩種調(diào)試模式。

          launch 指的是直接由編輯器啟動(dòng)(直接 fork 一個(gè)進(jìn)程),比如我們這個(gè)示例,而 attach 表示服務(wù)已經(jīng)啟動(dòng),我們是 attach 到原來(lái)那個(gè)進(jìn)程中,比如上面的 Chrome 調(diào)試。_ 然后打上斷點(diǎn),執(zhí)行就行了。

          執(zhí)行的時(shí)候,我們發(fā)現(xiàn)命令行會(huì)發(fā)現(xiàn)一段話(huà)。

          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

          第一個(gè) cd 忽略,我們主要看看中間這段。VSCode 啟動(dòng)的時(shí)候加載 bootloader.bundle.js 這個(gè)文件,然后傳了一堆 IPC 啟動(dòng)參數(shù),比如創(chuàng)建了一個(gè) sock 文件,其余的把 launch 里的參數(shù)翻譯了一下傳入。

          核心就是這個(gè) bootlaoder 文件,由于 VSCode 是 ts 寫(xiě)的,這個(gè)文件的源碼在這。

          https://github.com/microsoft/vscode-js-debug/blob/ca280351b2/src/targets/node/bootloader.ts

          最核心的代碼是 inspectOrQueue 方法,代碼如下,其中有幾個(gè)特別關(guān)鍵的地方。

          function inspectOrQueue(env: IBootloaderInfo): boolean {
          // 省略

          // 如果沒(méi)有傳 --inspect,則開(kāi)啟調(diào)試端口
          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)) {
          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) {
          // 同步模式,直接跟著應(yīng)用啟動(dòng),監(jiān)聽(tīng)調(diào)試端口
          spawnWatchdog(env.execPath || process.execPath, info);
          } else {

          // 異步模式,等進(jìn)程啟動(dòng),attach 監(jiān)聽(tīng)端口
          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 最基礎(chǔ)的 “開(kāi)啟端口”,“連接調(diào)試端口” 這兩個(gè)步驟。VSCode 還會(huì)考慮到別的場(chǎng)景,比如代碼創(chuàng)建子進(jìn)程時(shí),會(huì)將子進(jìn)程也自動(dòng)添加調(diào)試參數(shù),方便自動(dòng) attach 等。

          在這里,我們會(huì)發(fā)現(xiàn)一個(gè)新的名詞,叫 AutoAttach 。這是 VSCode 在 2018 年 7 月提出的新名詞,微軟表示用戶(hù)基本都不太會(huì)寫(xiě) launch.json 文件,經(jīng)常寫(xiě)錯(cuò)(沒(méi)錯(cuò),就是我),所以為了簡(jiǎn)化寫(xiě)法,特地做的新功能。

          這個(gè)功能怎么用呢?

          簡(jiǎn)單的來(lái)說(shuō),只要啟動(dòng)的 node 加上 --inspect 命令,VSCode 就能自動(dòng)監(jiān)視到,并且 attach 到進(jìn)程里開(kāi)啟調(diào)試,不再需要復(fù)雜的配置。開(kāi)啟的命令加到了選項(xiàng)里(cmd+shift+p 搜索)。

          有幾種附加方式。比較常用的是僅帶標(biāo)志。

          這樣我們只要在 VSCode 終端里輸入任意帶有 --inspect 的命令,就會(huì)自動(dòng)被斷點(diǎn)到了,很香。

          總結(jié)一下

          調(diào)試到這里基本就講完了,所有的調(diào)試的原理都是一樣的,藉由 Node.js 原生的打開(kāi)調(diào)試端口的能力,不同的 IDE 才能連接到該端口,進(jìn)而做出更加強(qiáng)大的能力。

          比如 VSCode 不僅僅能做傳統(tǒng)的調(diào)試,也能增加配置,在執(zhí)行調(diào)試前后增加鉤子,執(zhí)行自己的命令,這都是擴(kuò)展能力的體現(xiàn)。

          相信你看完這篇文章,對(duì) Node.js 應(yīng)用的調(diào)試方式有了一定的理解,寫(xiě)出更好的代碼。

          ??愛(ài)心三連擊

          1.看到這里了就點(diǎn)個(gè)在看支持下吧,你的點(diǎn)贊,在看是我創(chuàng)作的動(dòng)力。

          2.關(guān)注公眾號(hào)程序員成長(zhǎng)指北,回復(fù)「1」加入高級(jí)前端交流群!「在這里有好多 前端 開(kāi)發(fā)者,會(huì)討論 前端 Node 知識(shí),互相學(xué)習(xí)」!

          3.也可添加微信【ikoala520】,一起成長(zhǎng)。

          “在看轉(zhuǎn)發(fā)”是最大的支持

          瀏覽 73
          點(diǎn)贊
          評(píng)論
          收藏
          分享

          手機(jī)掃一掃分享

          分享
          舉報(bào)
          評(píng)論
          圖片
          表情
          推薦
          點(diǎn)贊
          評(píng)論
          收藏
          分享

          手機(jī)掃一掃分享

          分享
          舉報(bào)
          <kbd id="afajh"><form id="afajh"></form></kbd>
          <strong id="afajh"><dl id="afajh"></dl></strong>
            <del id="afajh"><form id="afajh"></form></del>
                1. <th id="afajh"><progress id="afajh"></progress></th>
                  <b id="afajh"><abbr id="afajh"></abbr></b>
                  <th id="afajh"><progress id="afajh"></progress></th>
                  怡红院久久| 亚洲九区 | 天天摸日日摸人人看 | 大香蕉尹人网 | 亚洲人免费视频 |