簡介
JavaScript 的強大功能之一,就是透過回呼函式以非同步方式運作。指派非同步回呼可讓您編寫事件驅動程式碼,但由於 JavaScript 並未以線性方式執行,因此追蹤錯誤會讓人感到頭痛。
幸運的是,現在您可以在 Chrome 開發人員工具中查看非同步 JavaScript 回呼的完整呼叫堆疊!

在 DevTools 中啟用非同步呼叫堆疊功能後,您就能在不同時間點深入瞭解網頁應用程式的狀態。針對部分事件監聽器、setInterval
、setTimeout
、XMLHttpRequest
、承諾、requestAnimationFrame
、MutationObservers
等,檢視完整的堆疊追蹤記錄。
在檢查堆疊追蹤時,您也可以分析執行階段執行期間的特定點中任何變數的值。就像是手錶表情符號的「時光機」!
讓我們啟用這項功能,並查看以下幾種情況。
在 Chrome 中啟用非同步偵錯功能
只要在 Chrome 中啟用這項新功能,即可試用。前往 Chrome Canary 開發人員工具的「Sources」面板。
在右側的「Call Stack」面板旁邊,有一個新的「Async」核取方塊。切換核取方塊,開啟或關閉非同步偵錯功能 (不過,一旦開啟,您可能永遠不會想關閉)。

擷取延遲計時器事件和 XHR 回應
你可能曾在 Gmail 中看到以下畫面:

如果傳送要求時發生問題 (伺服器發生問題,或用戶端端發生網路連線問題),Gmail 會在短暫的逾時後自動嘗試重新傳送郵件。
為了瞭解非同步呼叫堆疊如何協助我們分析延遲計時器事件和 XHR 回應,我使用模擬 Gmail 範例重建了這個流程。您可以在上述連結中找到完整的 JavaScript 程式碼,但流程如下:

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

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

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


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

評估先前範圍中的程式碼
除了簡單的監控運算式,您還可以在開發人員工具 JavaScript 控制台面板中,直接與先前範圍的程式碼互動。
假設您是 Dr. Who,需要比較進入 Tardis 前後的時間,您可以透過開發人員工具控制台,輕鬆評估、儲存及計算不同執行點的值。

在開發人員工具中操作運算式,可節省您切換回原始碼、進行編輯及重新整理瀏覽器的時間。
解開鏈結的承諾解析
如果您認為先前的模擬 Gmail 流程在未啟用非同步呼叫堆疊功能的情況下很難解開,那麼您能想像,如果是更複雜的非同步流程 (例如鏈結的承諾),難度會增加多少嗎?讓我們回顧 Jake Archibald JavaScript Promises 教學課程中的最後一個範例。
以下是 Jake async-best-example.html 範例中,檢視呼叫堆疊的簡短動畫。

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

太棒了!這類承諾。回呼過多。
取得網頁動畫的洞察資料
讓我們深入瞭解 HTML5Rocks 的檔案。還記得 Paul Lewis 的使用 requestAnimationFrame 製作更精簡、更強大、更快速的動畫嗎?
開啟 requestAnimationFrame 示範,並在 post.html 的 update() 方法開頭 (大約第 874 行) 新增中斷點。透過非同步呼叫堆疊,我們可以進一步瞭解 requestAnimationFrame,包括能夠一路回溯至啟動捲動事件回呼。


使用 MutationObserver 時追蹤 DOM 更新
MutationObserver
可讓我們觀察 DOM 中的變更。在這個簡單範例中,當您按一下按鈕時,系統會在 <div class="rows"></div>
中附加新的 DOM 節點。
在 demo.html 的 nodeAdded()
中新增中斷點 (第 31 行)。啟用非同步呼叫堆疊後,您現在可以透過 addNode()
回溯呼叫堆疊,回到初始點擊事件。


在非同步呼叫堆疊中偵錯 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 開發人員工具群組提供意見。