はじめに
JavaScript 独自の優れた機能として、コールバック関数を介して非同期に動作する機能があります。非同期コールバックを割り当てると、イベント ドリブンなコードを記述できますが、JavaScript が直線的に実行されていないため、バグを突き止めるのは難しい動作になります。
幸いなことに、Chrome DevTools で非同期 JavaScript コールバックの完全なコールスタックを表示できるようになりました。
DevTools で非同期コールスタック機能を有効にすると、さまざまな時点でのウェブアプリの状態を確認できます。イベント リスナー setInterval
、setTimeout
、XMLHttpRequest
、Promise、requestAnimationFrame
、MutationObservers
などのフルスタック トレースを確認します。
スタック トレースを調べる際に、ランタイム実行の特定ポイントでの変数の値を分析することもできます。腕時計を使うタイムマシンのようなものです。
この機能を有効にして、シナリオのいくつかを見てみましょう。
Chrome で非同期デバッグを有効にする
Chrome でこの機能を有効にして、新機能をお試しください。Chrome Canary DevTools の [ソース] パネルに移動します。
右側の [コールスタック] パネルの横に、[非同期] の新しいチェックボックスがあります。チェックボックスのオンとオフを切り替えて、非同期デバッグを有効または無効にします(一度有効にした場合でも、無効にする必要はありません)。
遅延タイマー イベントと XHR 応答をキャプチャする
Gmail で以下を目にしたことがある方も多いでしょう。
リクエストの送信に問題がある場合(サーバーに問題が発生しているか、クライアント側にネットワーク接続の問題がある場合)、Gmail は短いタイムアウト後に自動的にメッセージを再送信します。
遅延タイマー イベントと XHR レスポンスの分析に非同期コールスタックがどのように役立つかを確認するために、Gmail の模擬サンプルを使用してこのフローを再作成しました。JavaScript コード全体は上記のリンクにありますが、フローは次のとおりです。
以前のバージョンの DevTools で [Call Stack] パネルだけを見るだけで、postOnFail()
内のブレークポイントで、postOnFail()
がどこから呼び出されているかについて、情報はほとんど得られません。ただし、非同期スタックを有効にする場合の違いに注目してください。
非同期コールスタックをオンにすると、コールスタック全体を表示して、リクエストが submitHandler()
(送信ボタンのクリック後に発生)または retrySubmit()
(setTimeout()
の遅延後に発生)のどちらから開始されたかを簡単に確認できます。
非同期で式を監視します
コールスタック全体を走査すると、監視対象の式も更新され、その時点での状態が反映されます。
過去のスコープのコードを評価する
単に式を watch するだけでなく、DevTools の JavaScript コンソール パネルで、以前のスコープのコードを操作することもできます。
あなたがドクター・フーで、ターディスに乗る前と「現在」の時計を比べるのが少し手伝ってほしいとしましょう。DevTools コンソールから、さまざまな実行ポイントの値の評価、保存、計算を簡単に行うことができます。
DevTools 内で式を操作すると、ソースコードに戻って編集やブラウザの更新を行う手間を省けます。
チェーン プロミスの解決の解明
非同期コールスタック機能を有効にしなければ、以前の Gmail 模擬フローを解くのが難しいと思った場合、チェーン プロミスのようなより複雑な非同期フローでは、どれほど困難になるか想像できるでしょうか。JavaScript Promise に関する Jake Archibald のチュートリアルの最後の例に戻りましょう。
Jake の async-best-example.html サンプルでコールスタックをたどるアニメーションを次に示します。
ウェブ アニメーションに関する分析情報を取得する
HTML5Rocks のアーカイブを詳しく見ていきましょう。Paul Lewis の Leaner, Meaner, Faster Animations with 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 グループからお知らせください。