使用 Chrome 開發人員工具對非同步 JavaScript 進行偵錯

Pearl Chen

簡介

JavaScript 的強大功能之一,就是透過回呼函式以非同步方式運作。指派非同步回呼可讓您編寫事件驅動程式碼,但由於 JavaScript 並未以線性方式執行,因此追蹤錯誤會讓人感到頭痛。

幸運的是,現在您可以在 Chrome 開發人員工具中查看非同步 JavaScript 回呼的完整呼叫堆疊!

非同步呼叫堆疊的快速預覽總覽。
非同步呼叫堆疊的快速預覽總覽。 (我們很快就會分析這個示範的流程)。

在 DevTools 中啟用非同步呼叫堆疊功能後,您就能在不同時間點深入瞭解網頁應用程式的狀態。針對部分事件監聽器、setIntervalsetTimeoutXMLHttpRequest、承諾、requestAnimationFrameMutationObservers 等,檢視完整的堆疊追蹤記錄。

在檢查堆疊追蹤時,您也可以分析執行階段執行期間的特定點中任何變數的值。就像是手錶表情符號的「時光機」!

讓我們啟用這項功能,並查看以下幾種情況。

在 Chrome 中啟用非同步偵錯功能

只要在 Chrome 中啟用這項新功能,即可試用。前往 Chrome Canary 開發人員工具的「Sources」面板。

在右側的「Call Stack」面板旁邊,有一個新的「Async」核取方塊。切換核取方塊,開啟或關閉非同步偵錯功能 (不過,一旦開啟,您可能永遠不會想關閉)。

開啟或關閉非同步功能。

擷取延遲計時器事件和 XHR 回應

你可能曾在 Gmail 中看到以下畫面:

Gmail 嘗試重新傳送電子郵件。

如果傳送要求時發生問題 (伺服器發生問題,或用戶端端發生網路連線問題),Gmail 會在短暫的逾時後自動嘗試重新傳送郵件。

為了瞭解非同步呼叫堆疊如何協助我們分析延遲計時器事件和 XHR 回應,我使用模擬 Gmail 範例重建了這個流程。您可以在上述連結中找到完整的 JavaScript 程式碼,但流程如下:

模擬 Gmail 範例的流程圖。
上圖中以藍色標示的方法,是這項新 DevTool 功能最適合發揮效益的最佳位置,因為這些方法是異步運作。

單看舊版 DevTools 中的「Call Stack」面板,postOnFail() 中的暫停點幾乎不會提供任何關於 postOnFail() 呼叫來源的資訊。但請注意啟用非同步堆疊時的差異:

之前
在模擬 Gmail 範例中設定中斷點,但沒有非同步呼叫堆疊。
「Call Stack」面板啟用非同步功能。

您可以在這裡看到 postOnFail() 是透過 AJAX 回呼啟動的,但沒有其他資訊。

之後
在模擬 Gmail 範例中設定的暫停點,其中包含非同步呼叫堆疊。
啟用非同步功能的「Call Stack」面板

您可以看到 XHR 是從 submitHandler() 啟動的。太棒了!

啟用非同步呼叫堆疊後,您可以查看整個呼叫堆疊,輕鬆瞭解要求是否由 submitHandler() (按下提交按鈕後發生) 或 retrySubmit() (在 setTimeout() 延遲後發生) 啟動:

submitHandler()
在模擬 Gmail 範例中設定中斷點,並使用非同步呼叫堆疊
retrySubmit()
在模擬 Gmail 範例中設定另一個中斷點,並使用非同步呼叫堆疊

以非同步方式監控運算式

當您檢查完整的呼叫堆疊時,觀察到的運算式也會更新,反映當時的狀態!

使用監控運算式與非同步呼叫堆疊的範例

評估先前範圍中的程式碼

除了簡單的監控運算式,您還可以在開發人員工具 JavaScript 控制台面板中,直接與先前範圍的程式碼互動。

假設您是 Dr. Who,需要比較進入 Tardis 前後的時間,您可以透過開發人員工具控制台,輕鬆評估、儲存及計算不同執行點的值。

使用 JavaScript 主控台搭配非同步呼叫堆疊的範例。
請搭配使用 JavaScript 控制台和非同步呼叫堆疊,以便對程式碼進行偵錯。如要查看上述示範,請按這裡

在開發人員工具中操作運算式,可節省您切換回原始碼、進行編輯及重新整理瀏覽器的時間。

解開鏈結的承諾解析

如果您認為先前的模擬 Gmail 流程在未啟用非同步呼叫堆疊功能的情況下很難解開,那麼您能想像,如果是更複雜的非同步流程 (例如鏈結的承諾),難度會增加多少嗎?讓我們回顧 Jake Archibald JavaScript Promises 教學課程中的最後一個範例。

以下是 Jake async-best-example.html 範例中,檢視呼叫堆疊的簡短動畫。

之前
在無非同步呼叫堆疊的承諾範例中設定中斷點
「Call Stack」面板啟用非同步功能。

請注意,嘗試對承諾進行偵錯時,呼叫堆疊面板的資訊非常少。

之後
在含有非同步呼叫堆疊的承諾範例中設定中斷點。
啟用非同步功能的「Call Stack」面板

太棒了!這類承諾。回呼過多。

取得網頁動畫的洞察資料

讓我們深入瞭解 HTML5Rocks 的檔案。還記得 Paul Lewis 的使用 requestAnimationFrame 製作更精簡、更強大、更快速的動畫嗎?

開啟 requestAnimationFrame 示範,並在 post.html 的 update() 方法開頭 (大約第 874 行) 新增中斷點。透過非同步呼叫堆疊,我們可以進一步瞭解 requestAnimationFrame,包括能夠一路回溯至啟動捲動事件回呼。

之前
在 requestAnimationFrame 範例中設定的暫停點,沒有非同步呼叫堆疊。
「Call Stack」面板啟用非同步功能。
之後
在 requestAnimationFrame 範例中設定中斷點,並顯示非同步呼叫堆疊
並啟用非同步功能。

使用 MutationObserver 時追蹤 DOM 更新

MutationObserver 可讓我們觀察 DOM 中的變更。在這個簡單範例中,當您按一下按鈕時,系統會在 <div class="rows"></div> 中附加新的 DOM 節點。

在 demo.html 的 nodeAdded() 中新增中斷點 (第 31 行)。啟用非同步呼叫堆疊後,您現在可以透過 addNode() 回溯呼叫堆疊,回到初始點擊事件。

之前
在 mutationObserver 範例中設定的暫停點,不含非同步呼叫堆疊。
「Call Stack」面板啟用非同步功能。
之後
在 mutationObserver 範例中,使用非同步呼叫堆疊設定中斷點。
並啟用非同步功能。

在非同步呼叫堆疊中偵錯 JavaScript 的訣竅

為函式命名

如果您傾向將所有回呼指派為匿名函式,建議您改為為回呼命名,以便更輕鬆地查看呼叫堆疊。

舉例來說,請使用以下匿名函式:

window.addEventListener('load', function() {
  // do something
});

並為其命名,例如 windowLoaded()

window.addEventListener('load', function <strong>windowLoaded</strong>(){
  // do something
});

載入事件觸發時,會在 DevTools 堆疊追蹤中顯示其函式名稱,而非神秘的「(匿名函式)」。這樣一來,您就能輕鬆一覽堆疊追蹤中發生的情況。

變更前
匿名函式。
變更後
已命名函式

深入探索

回顧一下,以下是所有非同步回呼,DevTools 會在其中顯示完整的呼叫堆疊:

  • 計時器:回到 setTimeout()setInterval() 初始化的來源。
  • XHR:返回呼叫 xhr.send() 的位置。
  • 動畫影格:返回 requestAnimationFrame 的呼叫位置。
  • 承諾:返回已解析承諾的位置。
  • Object.observe:返回觀測器回呼原本繫結的位置。
  • MutationObservers:返回變化觀察器事件觸發的位置。
  • window.postMessage():檢查程序內訊息呼叫。
  • DataTransferItem.getAsString()
  • FileSystem API
  • IndexedDB
  • WebSQL
  • 透過 addEventListener() 傳送的符合資格的 DOM 事件:返回事件觸發的位置。基於效能考量,並非所有 DOM 事件都符合使用非同步呼叫堆疊功能的資格。目前可用的事件範例包括:'scroll'、'hashchange' 和 'selectionchange'。
  • 透過 addEventListener() 觸發的多媒體事件:返回事件觸發的位置。可用的多媒體事件包括:音訊和視訊事件 (例如「play」、「pause」、「ratechange」)、WebRTC MediaStreamTrackList 事件 (例如「addtrack」、「removetrack」) 和 MediaSource 事件 (例如「sourceopen」)。

您應該能夠查看 JavaScript 回呼的完整堆疊追蹤記錄,以便瞭解問題所在。當多個非同步事件彼此相關,或是在非同步回呼中擲回未偵測到的例外狀況時,這項 DevTools 功能就特別實用。

歡迎在 Chrome 中試用。如對這項新功能有任何意見,歡迎前往 Chrome 開發人員工具的 錯誤追蹤器Chrome 開發人員工具群組提供意見。