简介
JavaScript 的一项强大功能是它能够通过回调函数异步运行。分配异步回调可让您编写事件驱动型代码,但这也会让跟踪 bug 变得非常麻烦,因为 JavaScript 不是以线性方式执行。
幸运的是,现在您可以在 Chrome 开发者工具中查看异步 JavaScript 回调的完整调用堆栈!
在开发者工具中启用异步调用堆栈功能后,您将能够深入了解 Web 应用在不同时间点的状态。浏览部分事件监听器(setInterval
、setTimeout
、XMLHttpRequest
、promise、requestAnimationFrame
、MutationObservers
等)的完整堆栈轨迹。
在执行堆栈轨迹时,您还可以分析任何变量在该特定运行时执行点的值。它就像是手表表情的时光机!
让我们启用此功能,并尝试其中几种情况。
在 Chrome 中启用异步调试功能
在 Chrome 中启用这项新功能即可试用。前往 Chrome Canary 开发者工具的 Sources 面板。
在右侧 Call Stack 面板旁边,有一个新的“Async”复选框。切换复选框以开启或关闭异步调试。(尽管启用此功能后,您可能永远都想将其关闭)。
捕获延迟计时器事件和 XHR 响应
您以前可能在 Gmail 中遇到过类似情况:
如果发送请求时出现问题(服务器出现问题或客户端存在网络连接问题),Gmail 将在短暂超时后自动尝试重新发送邮件。
为了了解异步调用堆栈如何帮助我们分析延迟的计时器事件和 XHR 响应,我使用模拟 Gmail 示例重新创建了该流程。您可以在上面的链接中找到完整的 JavaScript 代码,但流程如下所示:
通过只查看先前版本的开发者工具中的“Call Stack”面板,postOnFail()
中的断点几乎可以让您了解从何处调用 postOnFail()
。不过,我们来看看开启异步堆栈时的差异:
启用异步调用堆栈后,您可以查看整个调用堆栈,以轻松查看请求是从 submitHandler()
(发生在点击提交按钮后发生)还是从 retrySubmit()
(发生在 setTimeout()
延迟后)发起的:
异步监视表达式
当您遍历整个调用堆栈时,受监视的表达式也会更新,以反映当时的状态!
评估既往范围内的代码
除了观察表达式之外,您还可以直接在开发者工具的 JavaScript 控制台面板中与先前作用域内的代码进行交互。
想象一下,你是哪些人在开发者工具控制台中,您可以轻松评估、存储和计算不同执行点中的值。
使用开发者工具进行操作您的表达式可以为您节省时间,而无需切换回源代码、进行修改和刷新浏览器。
探索链式 promise 解决方案
如果您认为在未启用异步调用堆栈功能的情况下,很难展开之前的模拟 Gmail 流程,可以想象一下,如果使用链接 Promise 等更复杂的异步流程,会有多困难?我们来回顾一下 Jake Archibald 关于 JavaScript Promise 的教程的最后一个示例。
下面是 Jake 的 async-best-example.html 示例中遍历调用堆栈的小动画。
深入了解网页动画
让我们更深入地了解 HTML5Rocks 归档。还记得 Paul Lewis 的 Leaner, Meaner, Faster Animations with 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
});
当加载事件触发时,该事件将显示在开发者工具堆栈轨迹中,并与其函数名称一起显示,而不是带有“(anonymous function)”含义的神秘函数。这样一来,您可以更轻松地一目了然地查看堆栈轨迹中发生的情况。
深入探索
简而言之,下面是开发者工具会显示完整调用堆栈的所有异步回调:
- 计时器:返回到初始化
setTimeout()
或setInterval()
的位置。 - XHR:返回到调用
xhr.send()
的位置。 - 动画帧:返回到调用
requestAnimationFrame
的位置。 - promise:返回已解析 promise 的位置。
- Object.observe:返回到最初绑定观察器回调的位置。
- MutationObservers:返回到触发 mutate 观察者事件的位置。
- 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 开发者工具 bug 跟踪器或 Chrome 开发者工具群组中给我们留言。