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

Pearl Chen

引言

JavaScript 的獨特功能透過回呼函式以非同步方式運作。指派非同步回呼可讓您編寫事件導向的程式碼,但也會由於 JavaScript 無法以線性方式執行,因此也會追蹤髮型錯誤。

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

非同步呼叫堆疊的簡要說明。
非同步呼叫堆疊的快速預告。 (我們很快就會詳細介紹這個示範的流程)。

在開發人員工具中啟用非同步呼叫堆疊功能後,您就能在不同時間點深入瞭解網頁應用程式的狀態。針對某些事件監聽器、setIntervalsetTimeoutXMLHttpRequest、承諾、requestAnimationFrameMutationObservers 等,進行完整的堆疊追蹤。

行走堆疊追蹤時,您也可以分析執行階段執行作業該特定時間點的任何變數值。就像手錶運算式的時光機一樣!

我們來啟用這項功能,並看看下列幾種情況。

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

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

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

開啟或關閉非同步功能。

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

您或許曾在 Gmail 中看過以下內容:

Gmail 正在重試傳送電子郵件。

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

如要瞭解非同步呼叫堆疊如何協助分析延遲計時器事件和 XHR 回應,我已使用 Gmail 模擬範例重新建立該流程。上方的連結中有完整的 JavaScript 程式碼,但其流程如下:

Gmail 模擬範例流程圖。
在上圖中,藍色標明的方法是最適當的程式碼位置,因為這些方法非同步運作。

光是查看舊版開發人員工具中的「Call Stack」面板,postOnFail() 中的中斷點可讓您瞭解從何處呼叫 postOnFail()。但是,請看看開啟非同步堆疊後的差異:

之前
以無非同步呼叫堆疊的模擬 Gmail 範例設定中斷點。
「未」啟用非同步的「Call Stack」面板。

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

之後
Gmail 模擬示例中設定的中斷點設有非同步呼叫堆疊。
啟用非同步的「呼叫堆疊」面板。

在這裡,您可以看到 XHR 是由 submitHandler() 啟動。很好!

啟用非同步呼叫堆疊後,您就可以查看整個呼叫堆疊,輕鬆查看要求是從 submitHandler() (按一下提交按鈕後發生),還是從 retrySubmit() (發生在 setTimeout() 延遲之後發生):

submitHandler()
Gmail 模擬示例中設定的中斷點設有非同步呼叫堆疊
retrySubmit()
透過非同步呼叫堆疊的 Gmail 模擬示例設定了另一個中斷點

以非同步方式查看運算式

當您步行完整呼叫堆疊時,已觀察的運算式也會更新,反映當下的狀態!

搭配使用觀察運算式與 aysnc 呼叫堆疊的範例

根據過去範圍評估程式碼

除了單純查看運算式以外,您也可以直接在開發人員工具 JavaScript 控制檯面板中與先前範圍的程式碼互動。

假設您是博士,需要一點幫助,才能比較你進入 Tardis 前的時鐘和「現在時間」。透過開發人員工具控制台,您可以輕鬆評估、儲存及計算不同執行階段的值。

JavaScript 控制台與 aysnc 呼叫堆疊搭配使用的範例。
搭配使用 JavaScript 控制台和非同步呼叫堆疊,即可對程式碼進行偵錯。您可以在這裡找到上述示範。

繼續使用開發人員工具操控運算式,就不必回想原始碼、進行編輯和重新整理瀏覽器。

拆解的承諾解決方案

如果您認為先前的模擬 Gmail 流程在未啟用非同步呼叫堆疊功能的情況下難以展開,您能想像一下,因為鏈結承諾等較為複雜的非同步流程會讓流程更加困難嗎?讓我們回顧 Jake Archibald 教學課程中的 JavaScript Promise 教學課程最終範例。

以下的動畫展示在 Jake 的 async-best-example.html 範例中,追蹤呼叫堆疊。

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

嘗試偵錯時,您會發現「Call Stack」面板在資訊上非常簡短。

之後
以非同步呼叫堆疊的承諾範例中設定的中斷點。
啟用非同步的「呼叫堆疊」面板。

太厲害了!這些承諾。多種回呼。

深入瞭解網頁動畫

讓我們深入瞭解 HTML5Rocks 封存檔案。還記得 Paul Lewis 的 Leaner、Meaner、Fast Animations 和 requestAnimationFrame 嗎?

開啟 requestAnimationFrame 示範,然後在 post.html 的 update() 方法 (約第 874 行) 的開頭加入中斷點。透過非同步呼叫堆疊,我們可更深入瞭解 requestAnimationFrame,包括逐步返回啟動捲動事件回呼。

之前
requestAnimationFrame 範例中設定的中斷點,沒有非同步呼叫堆疊。
「未」啟用非同步的「Call Stack」面板。
之後
requestAnimationFrame 範例中設定的中斷點,設有非同步呼叫堆疊
已啟用非同步。

使用 MutationObserver 時追蹤 DOM 更新

MutationObserver 可讓我們觀察 DOM 中的變化。在這個簡易範例中,點選按鈕後,系統就會將新的 DOM 節點附加至 <div class="rows"></div>

在 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
});

載入事件觸發時,會在開發人員工具堆疊追蹤中顯示其函式名稱,而非隱密的「(匿名函式)」。方便您快速掌握堆疊追蹤中的活動。

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

深入探索

總結來說,以下是開發人員工具會顯示完整呼叫堆疊的所有非同步回呼:

  • 計時器:返回 setTimeout()setInterval() 的初始化位置。
  • XHR:返回呼叫 xhr.send() 的位置。
  • 動畫頁框:返回呼叫 requestAnimationFrame 的位置。
  • Promise: 返回已解決承諾的問題。
  • 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 回呼的完整堆疊追蹤,建議您將這些頭髮留在頭上。當多個非同步事件彼此相關,或在非同步回呼中擲回未偵測到的例外狀況時,開發人員工具中的這項功能特別實用。

在 Chrome 中試試看。 如果您對這項新功能有任何意見,歡迎在 Chrome 開發人員工具錯誤追蹤工具Chrome 開發人員工具群組上留言。