Promiseとasync/await、デバッガーで追いかける非同期処理のデバッグ法
非同期処理のデバッグ、なぜ難しい?
Web開発において、ネットワーク通信やタイマー処理など、結果がすぐに返ってこない「非同期処理」は不可欠です。JavaScriptでは、Promise
やasync
/await
といった仕組みを使って非同期処理をより扱いやすく記述できるようになりました。しかし、これらの非同期処理は、同期処理とは異なる実行フローを持つため、いざバグが発生した際に原因を特定することが難しく感じられることがあります。
例えば、
- 予期せぬタイミングで処理が実行される
- エラーが発生しても適切に捕捉できない
- 変数の状態が思った通りになっていない
といった問題に直面することがあります。これは、非同期処理が完了するまで待たずに次の処理に進むため、従来のステップ実行だけでは処理の流れを追いかけにくいことに起因します。
この記事では、Promise
やasync
/await
を使ったJavaScript非同期処理のバグを効率的に特定するために、Web開発でよく利用されるデバッグツールであるChrome DevToolsとVS Codeのデバッガーをどのように活用すれば良いのかを具体的に解説します。
Chrome DevToolsを使った非同期デバッグ
Chrome DevToolsは、ブラウザ上で動作する強力なデバッグツールです。JavaScriptの非同期処理をデバッグする際にも、その機能は非常に役立ちます。
Sourcesタブの活用
JavaScriptのデバッグは、主にChrome DevToolsの「Sources」タブで行います。「Sources」タブでは、実行中のJavaScriptコードを確認したり、ブレークポイントを設定したり、ステップ実行したりすることができます。
- デバッグ対象のページを開く: デバッグしたいWebページをChromeブラウザで開きます。
- DevToolsを開く:
F12
キーを押すか、右クリックして「検証」を選択し、DevToolsを開きます。 - Sourcesタブを選択: 開いたDevToolsウィンドウの上部にあるタブメニューから「Sources」を選択します。
- デバッグ対象のファイルを開く: 画面左側のファイルツリーから、デバッグしたいJavaScriptファイルを選択します。
ブレークポイントの設定
非同期処理のデバッグでは、通常の同期処理と同様に、コードの特定の場所で実行を一時停止させる「ブレークポイント」を設定することが基本となります。
async
関数内await
の直後(処理が再開される場所)Promise
の.then()
,.catch()
,.finally()
メソッド内のコールバック関数- 非同期API(
setTimeout
,fetch
など)のコールバック関数
これらの箇所にブレークポイントを設定することで、非同期処理の各ステップでの状態を確認できます。設定したいコードの行番号の左側をクリックすると、ブレークポイントが設定されます。
ステップ実行と非同期コールスタック
ブレークポイントで実行が一時停止したら、画面右側のパネルにあるデバッグコントロールを使ってステップ実行を行います。
- Resume script execution (F8): 次のブレークポイントまで実行を再開します。
- Step over next function call (F10): 現在の行の関数呼び出しをステップオーバーします(関数内部に入らず、その関数が終了した次の行に進む)。
- Step into next function call (F11): 現在の行の関数呼び出しの中に入ります。非同期処理のコールバック関数の中身を確認したい場合に便利です。
- Step out of current function (Shift + F11): 現在実行中の関数から抜け出し、その関数を呼び出した次の行に進みます。
特に非同期処理のデバッグで重要になるのが、画面右側のパネル下部にある「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);
});
}
このコードのデバッグでは、以下のようにブレークポイントを設定できます。
fetchData
内のresolve(data)
行.then()
コールバック内のreturn processData(userData)
行- 2つ目の
.then()
コールバック内のconsole.log("Displaying..."
行 .catch()
コールバック内のconsole.error(...)
行
ブレークポイントで一時停止した際、画面右側の「Scope」パネルで、その時点での変数(userData
, processedData
など)の値を確認できます。「Call Stack」で非同期的な呼び出しの流れ(Promiseの解決など)を確認しながら、ステップ実行で処理の順序を追うことが、非同期処理の理解とバグ特定につながります。
VS Codeを使った非同期デバッグ
多くの開発者が利用するVS Codeにも、強力なデバッグ機能が組み込まれています。ブラウザ上で動作するWebアプリケーションのJavaScriptコードも、VS Codeから直接デバッグすることが可能です。
デバッグ設定 (launch.json)
VS Codeでブラウザ上のJavaScriptをデバッグするためには、デバッグ構成ファイルであるlaunch.json
を設定する必要があります。
- 実行とデバッグビューを開く: VS Codeのアクティビティバー(左端のアイコンが並んだエリア)にある「実行とデバッグ」アイコン(虫のマーク)をクリックします。
- launch.jsonの作成: デバッグビューが表示されたら、「launch.json ファイルを作成します」というリンクをクリックします。環境として「Web App (Chrome)」などを選択します。(プロジェクトの種類によって選択肢は異なります。HTMLファイルを開いている場合は「Web App (Chrome)」が適切です。)
- 設定の確認/編集:
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のエディタ上でコードの行番号の左側をクリックすることでブレークポイントを設定できます。
非同期処理のデバッグでは、以下の場所にブレークポイントを置くのが有効です。
async
関数、await
式の行Promise
の.then()
,.catch()
内のコールバック関数- 非同期API(
fetch
など)の呼び出しや、その結果を受け取る部分
デバッグ実行中にブレークポイントで一時停止すると、VS Codeのデバッグビューに「変数」「ウォッチ」「コールスタック」などのパネルが表示されます。
- 変数: その時点でのローカル変数やグローバル変数の値を確認できます。
- ウォッチ: 特定の変数や式の値を常に監視できます。
- コールスタック: 関数の呼び出し履歴が表示されます。VS Codeのデバッガーも、非同期処理に関するスタック情報を表示する能力があります(バージョンや非同期処理のタイプによる)。これにより、非同期処理の実行パスを理解する助けになります。
ステップ実行の操作は、デバッグビュー上部のコントロールバーや、エディタ上部(一時停止時)に表示されるボタンで行います。機能はChrome DevToolsのステップ実行と同様です。
- 続ける (F5)
- ステップオーバー (F10)
- ステップイン (F11)
- ステップアウト (Shift + F11)
特に 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) を使うと、fetch
や json
の内部処理(デバッグシンボルがあれば)に入ることができますが、通常はステップオーバー (F10) を使って、await
が完了して結果が返ってきた後の行に移動するのが一般的です。
ステップ実行中に「変数」パネルを見れば、response
オブジェクトや user
オブジェクトの内容を確認できます。catch
ブロックにブレークポイントを設定しておけば、非同期処理中に発生したエラーが適切に捕捉されているか、エラーオブジェクトの内容は何かを確認できます。
非同期デバッグのヒント
- console.logとの併用: デバッガーだけでは実行順序を正確に把握しにくい非同期処理では、要所に
console.log
を挟み、タイムスタンプと共に出力することで、実際の処理順序を確認することが非常に有効です。 - エラーハンドリング: 非同期処理でエラーが発生した場合、適切に
catch
されないと、デバッガーでも追跡が難しくなることがあります。Promise
の.catch()
やasync
/await
のtry...catch
を正しく実装することが、デバッグの第一歩です。 - 原因の切り分け: 問題が発生している非同期処理の塊を特定し、その部分に絞ってデバッグを行います。必要に応じて、問題を再現できる最小限のコード断片を作成することも有効です。
- 非同期コールスタックの活用: DevToolsやVS Codeのコールスタックパネルに表示される非同期関連の情報は、処理がどのように呼び出されたか、その流れを理解する上で貴重な手がかりとなります。表示される情報を注意深く観察してください。
まとめ
Promise
やasync
/await
を使った非同期処理はJavaScriptの強力な機能ですが、その非同期性ゆえにデバッグが難しく感じられることがあります。しかし、Chrome DevToolsやVS Codeといったデバッグツールを適切に活用することで、非同期処理の実行フローを追いかけ、変数の状態を確認し、エラーの発生箇所と原因を特定することが可能です。
この記事で解説したブレークポイントの設定、ステップ実行、そして非同期コールスタックの確認といった基本的なデバッグ手法は、非同期処理のバグに立ち向かう上で非常に役立ちます。これらのツールとテクニックを習得し、日々の開発におけるデバッグ効率を向上させていきましょう。継続的に実践することで、非同期処理のコードに対する理解も深まり、より堅牢なアプリケーションを開発できるようになります。