デバッグツール比較&活用

Promiseとasync/await、デバッガーで追いかける非同期処理のデバッグ法

Tags: JavaScript, デバッグ, 非同期処理, Chrome DevTools, VS Code

非同期処理のデバッグ、なぜ難しい?

Web開発において、ネットワーク通信やタイマー処理など、結果がすぐに返ってこない「非同期処理」は不可欠です。JavaScriptでは、Promiseasync/awaitといった仕組みを使って非同期処理をより扱いやすく記述できるようになりました。しかし、これらの非同期処理は、同期処理とは異なる実行フローを持つため、いざバグが発生した際に原因を特定することが難しく感じられることがあります。

例えば、

といった問題に直面することがあります。これは、非同期処理が完了するまで待たずに次の処理に進むため、従来のステップ実行だけでは処理の流れを追いかけにくいことに起因します。

この記事では、Promiseasync/awaitを使ったJavaScript非同期処理のバグを効率的に特定するために、Web開発でよく利用されるデバッグツールであるChrome DevToolsとVS Codeのデバッガーをどのように活用すれば良いのかを具体的に解説します。

Chrome DevToolsを使った非同期デバッグ

Chrome DevToolsは、ブラウザ上で動作する強力なデバッグツールです。JavaScriptの非同期処理をデバッグする際にも、その機能は非常に役立ちます。

Sourcesタブの活用

JavaScriptのデバッグは、主にChrome DevToolsの「Sources」タブで行います。「Sources」タブでは、実行中のJavaScriptコードを確認したり、ブレークポイントを設定したり、ステップ実行したりすることができます。

  1. デバッグ対象のページを開く: デバッグしたいWebページをChromeブラウザで開きます。
  2. DevToolsを開く: F12キーを押すか、右クリックして「検証」を選択し、DevToolsを開きます。
  3. Sourcesタブを選択: 開いたDevToolsウィンドウの上部にあるタブメニューから「Sources」を選択します。
  4. デバッグ対象のファイルを開く: 画面左側のファイルツリーから、デバッグしたいJavaScriptファイルを選択します。

ブレークポイントの設定

非同期処理のデバッグでは、通常の同期処理と同様に、コードの特定の場所で実行を一時停止させる「ブレークポイント」を設定することが基本となります。

これらの箇所にブレークポイントを設定することで、非同期処理の各ステップでの状態を確認できます。設定したいコードの行番号の左側をクリックすると、ブレークポイントが設定されます。

ステップ実行と非同期コールスタック

ブレークポイントで実行が一時停止したら、画面右側のパネルにあるデバッグコントロールを使ってステップ実行を行います。

特に非同期処理のデバッグで重要になるのが、画面右側のパネル下部にある「Call Stack」セクションです。通常のコールスタックは同期的な関数の呼び出し履歴を示しますが、Chrome DevToolsでは非同期処理の呼び出しに関連するスタックも表示されることがあります。これにより、非同期処理がどのようにして現在実行されている箇所にたどり着いたのか、その「因果関係」を追跡するヒントを得られます。

具体例:Promiseチェーンのデバッグ

function fetchData(url) {
  return new Promise((resolve, reject) => {
    setTimeout(() => {
      const data = `Data from ${url}`;
      console.log(`Fetched: ${data}`);
      resolve(data);
    }, 1000);
  });
}

fetchData("api/users")
  .then(userData => {
    console.log("Processing user data...");
    return processData(userData); // processDataはPromiseを返すとする
  })
  .then(processedData => {
    console.log("Displaying processed data:", processedData);
  })
  .catch(error => {
    console.error("An error occurred:", error);
  });

function processData(data) {
  return new Promise(resolve => {
     setTimeout(() => {
       const processed = data.toUpperCase();
       console.log("Processed:", processed);
       resolve(processed);
     }, 500);
  });
}

このコードのデバッグでは、以下のようにブレークポイントを設定できます。

ブレークポイントで一時停止した際、画面右側の「Scope」パネルで、その時点での変数(userData, processedData など)の値を確認できます。「Call Stack」で非同期的な呼び出しの流れ(Promiseの解決など)を確認しながら、ステップ実行で処理の順序を追うことが、非同期処理の理解とバグ特定につながります。

VS Codeを使った非同期デバッグ

多くの開発者が利用するVS Codeにも、強力なデバッグ機能が組み込まれています。ブラウザ上で動作するWebアプリケーションのJavaScriptコードも、VS Codeから直接デバッグすることが可能です。

デバッグ設定 (launch.json)

VS Codeでブラウザ上のJavaScriptをデバッグするためには、デバッグ構成ファイルであるlaunch.jsonを設定する必要があります。

  1. 実行とデバッグビューを開く: VS Codeのアクティビティバー(左端のアイコンが並んだエリア)にある「実行とデバッグ」アイコン(虫のマーク)をクリックします。
  2. launch.jsonの作成: デバッグビューが表示されたら、「launch.json ファイルを作成します」というリンクをクリックします。環境として「Web App (Chrome)」などを選択します。(プロジェクトの種類によって選択肢は異なります。HTMLファイルを開いている場合は「Web App (Chrome)」が適切です。)
  3. 設定の確認/編集: launch.jsonファイルが作成されます。configurations配列の中にデバッグ構成が記述されています。"url"プロパティには、デバッグ対象のWebサイトのURLを指定します。必要に応じて、ローカル開発サーバーのURLなどに変更します。
{
    "version": "0.2.0",
    "configurations": [
        {
            "type": "chrome",
            "request": "launch",
            "name": "Launch Chrome against localhost",
            "url": "http://localhost:8080", // 開発サーバーのURLに合わせる
            "webRoot": "${workspaceFolder}"
        }
    ]
}

この設定後、デバッグビューの上部にあるドロップダウンリストから作成した構成(例: "Launch Chrome against localhost")を選択し、緑色の開始ボタンをクリックすると、指定したURLがChromeブラウザで開き、VS Codeのデバッガーがアタッチされます。

ブレークポイントとステップ実行

Chrome DevToolsと同様に、VS Codeのエディタ上でコードの行番号の左側をクリックすることでブレークポイントを設定できます。

非同期処理のデバッグでは、以下の場所にブレークポイントを置くのが有効です。

デバッグ実行中にブレークポイントで一時停止すると、VS Codeのデバッグビューに「変数」「ウォッチ」「コールスタック」などのパネルが表示されます。

ステップ実行の操作は、デバッグビュー上部のコントロールバーや、エディタ上部(一時停止時)に表示されるボタンで行います。機能はChrome DevToolsのステップ実行と同様です。

特に await の行でステップオーバーやステップインを行うと、非同期処理が完了するまでデバッガーが待機し、処理が再開された箇所へ自動的に移動します。これにより、非同期処理を同期処理のように追跡することが可能になります。

具体例:async/awaitのデバッグ

async function fetchAndProcessUser(userId) {
  try {
    console.log(`Fetching user: ${userId}`);
    const response = await fetch(`/api/users/${userId}`); // ここで待機
    if (!response.ok) {
      throw new Error(`HTTP error! status: ${response.status}`);
    }
    const user = await response.json(); // ここで待機
    console.log("User data:", user);

    const processed = processUserData(user); // 同期処理とする
    console.log("Processed data:", processed);
    return processed;

  } catch (error) {
    console.error("Error in fetchAndProcessUser:", error);
    throw error; // エラーを再スロー
  }
}

fetchAndProcessUser(123)
  .then(result => {
    console.log("Final result:", result);
  })
  .catch(err => {
    console.error("Caught error at top level:", err);
  });

function processUserData(user) {
  // ... 処理 ...
  return user.name.toUpperCase();
}

この例では、await fetch(...)await response.json() の行にブレークポイントを設定します。デバッグを開始し、これらの行に到達すると実行が一時停止します。ステップイン (F11) を使うと、fetchjson の内部処理(デバッグシンボルがあれば)に入ることができますが、通常はステップオーバー (F10) を使って、await が完了して結果が返ってきた後の行に移動するのが一般的です。

ステップ実行中に「変数」パネルを見れば、response オブジェクトや user オブジェクトの内容を確認できます。catch ブロックにブレークポイントを設定しておけば、非同期処理中に発生したエラーが適切に捕捉されているか、エラーオブジェクトの内容は何かを確認できます。

非同期デバッグのヒント

まとめ

Promiseasync/awaitを使った非同期処理はJavaScriptの強力な機能ですが、その非同期性ゆえにデバッグが難しく感じられることがあります。しかし、Chrome DevToolsやVS Codeといったデバッグツールを適切に活用することで、非同期処理の実行フローを追いかけ、変数の状態を確認し、エラーの発生箇所と原因を特定することが可能です。

この記事で解説したブレークポイントの設定、ステップ実行、そして非同期コールスタックの確認といった基本的なデバッグ手法は、非同期処理のバグに立ち向かう上で非常に役立ちます。これらのツールとテクニックを習得し、日々の開発におけるデバッグ効率を向上させていきましょう。継続的に実践することで、非同期処理のコードに対する理解も深まり、より堅牢なアプリケーションを開発できるようになります。