はじめに
JavaScript を独自たらしめている強力な機能の 1 つが、コールバック関数を介して非同期で動作する機能です。非同期コールバックを割り当てると、イベントドリブン コードを記述できますが、JavaScript がリニアに実行されないため、バグの追跡が困難になります。
幸い、Chrome DevTools では、非同期 JavaScript コールバックの完全なコールスタックを確認できます。
DevTools で非同期呼び出しスタック機能を有効にすると、さまざまな時点でウェブアプリの状態をドリルダウンできます。一部のイベント リスナー(setInterval
、setTimeout
、XMLHttpRequest
、Promise、requestAnimationFrame
、MutationObservers
など)の完全なスタック トレースを確認します。
スタック トレースを確認する際に、ランタイム実行の特定の時点での変数の値を分析することもできます。ウォッチフェイスのタイムマシンのようなものです。
この機能を有効にして、いくつかのシナリオを見てみましょう。
Chrome で非同期デバッグを有効にする
この新機能をお試しいただくには、Chrome で有効にしてください。Chrome Canary DevTools の [Sources] パネルに移動します。
右側の [Call Stack] パネルの横に、[Async] の新しいチェックボックスがあります。チェックボックスをオンまたはオフにして、非同期デバッグを有効または無効にします(一度オンにすると、オフにすることはほとんどありません)。
遅延したタイマー イベントと XHR レスポンスをキャプチャする
Gmail で以下のようなメッセージが表示されたことがあると思います。
リクエストの送信で問題が発生した場合(サーバーに問題があるか、クライアント側のネットワーク接続に問題がある場合)、Gmail は短いタイムアウト後にメッセージを自動的に再送信しようとします。
非同期呼び出しスタックが遅延したタイマー イベントと XHR レスポンスを分析するうえでどのように役立つかを確認するため、Gmail のモック例でそのフローを再現しました。JavaScript コード全体は上記のリンクから確認できますが、フローとしては次のようになります。
以前のバージョンの DevTools の [Call Stack] パネルのみを見ると、postOnFail()
内のブレークポイントから postOnFail()
が呼び出された場所に関する情報がほとんど得られませんでした。ただし、非同期スタックをオンにしたときの違いを確認してください。
非同期呼び出しスタックをオンにすると、呼び出しスタック全体を表示して、リクエストが submitHandler()
(送信ボタンのクリック後に発生)から開始されたのか、retrySubmit()
(setTimeout()
遅延後に発生)から開始されたのかを簡単に確認できます。
Watch 式を非同期で評価する
完全なコールスタックを走査すると、ウォッチ対象の式も更新され、その時点での状態が反映されます。
過去のスコープのコードを評価する
式を単に監視するだけでなく、DevTools の JavaScript コンソール パネルで、前のスコープのコードと直接やり取りできます。
ドクター・フーだと想像してみてください。ターディスに乗る前と「今」の時刻を比較する際に、少し助けが必要だとします。DevTools コンソールでは、さまざまな実行ポイントの値を簡単に評価、保存、計算できます。
DevTools 内で式を操作すると、ソースコードに戻って編集し、ブラウザを更新する手間が省けます。
チェーンされた Promise の解決を解きほぐす
前述の Gmail のモックフローは、非同期呼び出しスタック機能を有効にしないと解明が難しいと思われたでしょうか。チェーンされたプロミスなどの複雑な非同期フローで、どれほど解明が難しくなるか想像できますか?Jake Archibald の JavaScript Promises に関するチュートリアルの最後の例を見てみましょう。
以下は、Jake の async-best-example.html の例で呼び出しスタックをウォークするアニメーションです。
ウェブ アニメーションに関する分析情報を取得する
HTML5Rocks のアーカイブを詳しく見てみましょう。Paul Lewis 氏の requestAnimationFrame による軽量で高速なアニメーションを覚えていますか?
requestAnimationFrame デモを開き、post.html の update() メソッドの先頭(874 行目付近)にブレークポイントを追加します。非同期コールスタックを使用すると、requestAnimationFrame に関する多くの分析情報を取得できます。たとえば、開始したスクロール イベントのコールバックまで遡ることができます。
MutationObserver を使用した DOM の更新を追跡する
MutationObserver
を使用すると、DOM の変更を監視できます。この簡単な例では、ボタンをクリックすると、新しい DOM ノードが <div class="rows"></div>
に追加されます。
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
が呼び出された場所まで戻ります。 - Promise: 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 コールバックの完全なスタック トレースを確認できるため、頭髪を失う心配はありません。DevTools のこの機能は、複数の非同期イベントが相互に関連して発生する場合や、非同期コールバック内から未捕捉の例外がスローされた場合に特に役立ちます。
Chrome で試してみてください。 この新機能についてフィードバックがある場合は、Chrome DevTools のバグトラッカーまたは Chrome DevTools グループでお問い合わせください。