Chrome DevTools を使用して非同期 JavaScript をデバッグする

はじめに

JavaScript 独自の優れた機能として、コールバック関数を介して非同期に動作する機能があります。非同期コールバックを割り当てると、イベント ドリブンなコードを記述できますが、JavaScript が直線的に実行されていないため、バグを突き止めるのは難しい動作になります。

幸いなことに、Chrome DevTools で非同期 JavaScript コールバックの完全なコールスタックを表示できるようになりました。

非同期コールスタックの概要に関する簡単なティーザー。
非同期コールスタックの概要に関するティーザー。 (このデモのフローは後ほど詳しく説明します)。

DevTools で非同期コールスタック機能を有効にすると、さまざまな時点でのウェブアプリの状態を確認できます。イベント リスナー setIntervalsetTimeoutXMLHttpRequest、Promise、requestAnimationFrameMutationObservers などのフルスタック トレースを確認します。

スタック トレースを調べる際に、ランタイム実行の特定ポイントでの変数の値を分析することもできます。腕時計を使うタイムマシンのようなものです。

この機能を有効にして、シナリオのいくつかを見てみましょう。

Chrome で非同期デバッグを有効にする

Chrome でこの機能を有効にして、新機能をお試しください。Chrome Canary DevTools の [ソース] パネルに移動します。

右側の [コールスタック] パネルの横に、[非同期] の新しいチェックボックスがあります。チェックボックスのオンとオフを切り替えて、非同期デバッグを有効または無効にします(一度有効にした場合でも、無効にする必要はありません)。

非同期機能をオンまたはオフにします。

遅延タイマー イベントと XHR 応答をキャプチャする

Gmail で以下を目にしたことがある方も多いでしょう。

Gmail がメールの送信を再試行しています。

リクエストの送信に問題がある場合(サーバーに問題が発生しているか、クライアント側にネットワーク接続の問題がある場合)、Gmail は短いタイムアウト後に自動的にメッセージを再送信します。

遅延タイマー イベントと XHR レスポンスの分析に非同期コールスタックがどのように役立つかを確認するために、Gmail の模擬サンプルを使用してこのフローを再作成しました。JavaScript コード全体は上記のリンクにありますが、フローは次のとおりです。

模擬的な Gmail のサンプルのフローチャート。
上の図の青色でハイライト表示されたメソッドは、非同期に機能するため、この新しい DevTool 機能が最も有益なところを目立たせています。

以前のバージョンの DevTools で [Call Stack] パネルだけを見るだけで、postOnFail() 内のブレークポイントで、postOnFail() がどこから呼び出されているかについて、情報はほとんど得られません。ただし、非同期スタックを有効にする場合の違いに注目してください。

非同期コールスタックのないモックアップの Gmail サンプルで設定されたブレークポイント。
非同期が無効な場合の [コールスタック] パネル

ご覧のように、postOnFail() が AJAX コールバックから開始されましたが、それ以上の情報はありません。

変更後
非同期コールスタックを使用する Gmail のモックアップの例で設定されたブレークポイント。
非同期が有効になっているコールスタック パネル。

ここでは、XHR が submitHandler() から開始されていることがわかります。成功です。

非同期コールスタックをオンにすると、コールスタック全体を表示して、リクエストが submitHandler()(送信ボタンのクリック後に発生)または retrySubmit()setTimeout() の遅延後に発生)のどちらから開始されたかを簡単に確認できます。

submitHandler()
非同期コールスタックを使用するモックアップの Gmail サンプルで設定されたブレークポイント
retrySubmit()
非同期コールスタックを使用するモックアップの Gmail サンプルで設定された別のブレークポイント

非同期で式を監視します

コールスタック全体を走査すると、監視対象の式も更新され、その時点での状態が反映されます。

aysnc コールスタックでの watch 式の使用例

過去のスコープのコードを評価する

単に式を watch するだけでなく、DevTools の JavaScript コンソール パネルで、以前のスコープのコードを操作することもできます。

あなたがドクター・フーで、ターディスに乗る前と「現在」の時計を比べるのが少し手伝ってほしいとしましょう。DevTools コンソールから、さまざまな実行ポイントの値の評価、保存、計算を簡単に行うことができます。

JavaScript コンソールの aysnc コールスタックの使用例。
JavaScript コンソールを非同期コールスタックと組み合わせて使用して、コードをデバッグします。上記のデモはこちらでご覧いただけます。

DevTools 内で式を操作すると、ソースコードに戻って編集やブラウザの更新を行う手間を省けます。

チェーン プロミスの解決の解明

非同期コールスタック機能を有効にしなければ、以前の Gmail 模擬フローを解くのが難しいと思った場合、チェーン プロミスのようなより複雑な非同期フローでは、どれほど困難になるか想像できるでしょうか。JavaScript Promise に関する Jake Archibald のチュートリアルの最後の例に戻りましょう。

Jake の async-best-example.html サンプルでコールスタックをたどるアニメーションを次に示します。

非同期コールスタックのない Promise の例でブレークポイントを設定
非同期が無効な場合の [コールスタック] パネル

Promise をデバッグしようとすると、[Call Stack] パネルに非常に短い情報が表示されることに注目してください。

変更後
非同期コールスタックを使用した Promise の例で設定されたブレークポイント。
非同期が有効になっているコールスタック パネル。

結果を確認しましょう。そういう約束だよ。多数のコールバック。

ウェブ アニメーションに関する分析情報を取得する

HTML5Rocks のアーカイブを詳しく見ていきましょう。Paul Lewis の Leaner, Meaner, Faster Animations with requestAnimationFrame を覚えていますか。

requestAnimationFrame デモを開き、post.html の update() メソッドの先頭(874 行目付近)にブレークポイントを追加します。非同期コールスタックを使用すると、スクロールの開始イベント コールバックまで遡ることができる機能など、requestAnimationFrame についてより多くの分析情報を得ることができます。

非同期コールスタックのない requestAnimationFrame の例でブレークポイントを設定しました。
非同期が無効な場合の [コールスタック] パネル
変更後
非同期コールスタックを使用した requestAnimationFrame の例でブレークポイントを設定
非同期有効化

MutationObserver 使用時の DOM 更新の追跡

MutationObserver を使用すると、DOM の変化を監視できます。この簡単な例では、ボタンをクリックすると、新しい DOM ノードが <div class="rows"></div> に追加されます。

demo.html の nodeAdded()(31 行目)内にブレークポイントを追加します。非同期コールスタックを有効にすると、addNode() を通じてコールスタックを最初のクリック イベントまで遡ることができます。

非同期コールスタックのない mutionObserver の例で設定されたブレークポイント。
非同期が無効な場合の [コールスタック] パネル
変更後
非同期コールスタックを使用する mutObserver の例で設定されたブレークポイント。
非同期有効化

非同期コールスタックでの 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 グループからお知らせください。