はじめに
「async/awaitを使っているのに、なぜかundefinedが返ってくる...」
「エラーが発生しているのに、try-catchでキャッチできない...」
「コードは動いているけど、順番がおかしい...」
JavaScriptのasync/awaitを学び始めたばかりの頃、このような経験をしたことはありませんか?
async/awaitはPromiseをより読みやすく書くための構文ですが、Promiseの理解が不十分だと、思わぬところでハマってしまいます。
この記事では、初心者が必ず詰まる3つのポイントと、その解決方法を実際のコード例とともに解説します。
なぜasync/awaitで混乱するのか?
async/awaitはPromiseの糖衣構文
まず、重要なことを理解しましょう。
async/awaitはPromiseの別の書き方です。 内部的にはPromiseと同じ動作をしています。
// Promiseを使った場合
function fetchUserData() {
return fetch('/api/user')
.then(response => response.json())
.then(data => {
return data;
})
.catch(error => {
throw error;
});
}
// async/awaitを使った場合(同じ動作)
async function fetchUserData() {
try {
const response = await fetch('/api/user');
const data = await response.json();
return data;
} catch (error) {
throw error;
}
}
見た目は同期的なコードのように見えますが、実際には非同期処理です。この「同期的に見えるけど非同期」という点が、混乱の原因になります。
よくある間違い1: awaitを忘れる → undefinedが返ってくる
問題のコード
async function getUserName() {
const response = fetch('/api/user'); // awaitを忘れている!
const data = response.json(); // エラー: response.json is not a function
return data.name;
}
const name = getUserName(); // Promiseオブジェクトが返ってくる
console.log(name); // Promise { <pending> }
なぜ動かないのか?
fetch()はPromiseを返します。awaitを付けないと、Promiseオブジェクトそのものが返ってきます。
// awaitなし → Promiseオブジェクトが返る
const response = fetch('/api/user');
console.log(response); // Promise { <pending> }
// awaitあり → 実際のレスポンスが返る
const response = await fetch('/api/user');
console.log(response); // Response { ... }
解決方法
すべてのPromiseを返す処理にawaitを付けましょう。
async function getUserName() {
const response = await fetch('/api/user'); // ✅ awaitを追加
const data = await response.json(); // ✅ awaitを追加
return data.name;
}
// 呼び出す側もawaitが必要
const name = await getUserName(); // ✅ awaitを追加
console.log(name); // "山田太郎"
チェックポイント
- Promiseを返す関数(
fetch,response.json()など)にawaitを付けているか? - 非同期関数を呼び出す側でも
awaitを付けているか?
よくある間違い2: try-catchを忘れる → エラーがキャッチできない
問題のコード
async function fetchData() {
const response = await fetch('/api/data'); // エラーが発生する可能性がある
const data = await response.json();
return data;
}
// エラーが発生してもキャッチされない
const data = await fetchData(); // Uncaught (in promise) Error
console.log(data); // 実行されない
なぜ動かないのか?
async/awaitを使うと、エラーはPromiseのrejectとして扱われます。try-catchがないと、エラーが上位に伝播してしまいます。
// エラーが発生した場合
async function fetchData() {
await fetch('/api/data'); // ネットワークエラー
// この時点でPromiseがrejectされる
// try-catchがないと、エラーがそのまま上位に伝播
}
解決方法
必ずtry-catchでエラーハンドリングしましょう。
async function fetchData() {
try {
const response = await fetch('/api/data');
const data = await response.json();
return data;
} catch (error) {
// ✅ エラーをキャッチ
console.error('データの取得に失敗しました:', error);
return null; // または適切なデフォルト値
}
}
// 安全に呼び出せる
const data = await fetchData();
if (data) {
console.log(data);
}
チェックポイント
- 非同期処理を実行する関数にtry-catchを付けているか?
- エラー時の処理(ログ出力、デフォルト値の返却など)を実装しているか?
よくある間違い3: 非同期関数の呼び出しを忘れる → 順番がおかしい
問題のコード
async function processData() {
console.log('1. 開始');
fetchUserData(); // awaitを忘れている!
fetchPostData(); // awaitを忘れている!
console.log('2. 完了'); // これが先に実行される
}
processData();
// 出力:
// 1. 開始
// 2. 完了 ← データ取得より先に実行される!
// (後で) ユーザーデータ
// (後で) 投稿データ
なぜ動かないのか?
awaitを付けないと、非同期処理の完了を待たずに次の処理に進んでしまいます。
// awaitなし → 並列実行(順番が保証されない)
fetchUserData(); // バックグラウンドで実行
fetchPostData(); // バックグラウンドで実行
console.log('完了'); // すぐに実行される
// awaitあり → 順次実行(順番が保証される)
await fetchUserData(); // 完了を待つ
await fetchPostData(); // 完了を待つ
console.log('完了'); // 両方の処理が終わってから実行
解決方法1: 順次実行(1つずつ処理)
async function processData() {
console.log('1. 開始');
const userData = await fetchUserData(); // ✅ 完了を待つ
const postData = await fetchPostData(); // ✅ 完了を待つ
console.log('2. 完了'); // 両方の処理が終わってから実行
return { userData, postData };
}
解決方法2: 並列実行(同時に処理)
複数の処理を同時に実行したい場合は、Promise.allを使います。
async function processData() {
console.log('1. 開始');
// ✅ Promise.allで並列実行
const [userData, postData] = await Promise.all([
fetchUserData(),
fetchPostData()
]);
console.log('2. 完了'); // 両方の処理が終わってから実行
return { userData, postData };
}
チェックポイント
- 非同期処理の完了を待つ必要がある場合は
awaitを付けているか? - 複数の処理を並列実行したい場合は
Promise.allを使っているか?
デバッグ方法
1. Promiseの状態を確認する
awaitを付け忘れている場合、Promiseオブジェクトが返ってきます。
const result = fetch('/api/data'); // awaitなし
console.log(result); // Promise { <pending> } ← Promiseオブジェクト
const result = await fetch('/api/data'); // awaitあり
console.log(result); // Response { ... } ← 実際のデータ
2. エラーメッセージを確認する
ブラウザのコンソールやNode.jsのエラーメッセージを確認しましょう。
// よくあるエラーメッセージ
// "response.json is not a function" → awaitを忘れている
// "Uncaught (in promise)" → try-catchを忘れている
// "undefined" → awaitを忘れている
3. 実行順序を確認する
console.logで実行順序を確認しましょう。
async function debugExample() {
console.log('1. 開始');
const data = await fetchData();
console.log('2. データ取得完了', data);
console.log('3. 終了');
}
実践例: よくあるパターン
パターン1: APIからデータを取得して表示
// ❌ 間違い
async function displayUser() {
const response = fetch('/api/user');
const data = response.json();
document.getElementById('name').textContent = data.name;
}
// ✅ 正しい
async function displayUser() {
try {
const response = await fetch('/api/user');
const data = await response.json();
document.getElementById('name').textContent = data.name;
} catch (error) {
console.error('ユーザー情報の取得に失敗しました:', error);
document.getElementById('name').textContent = '取得失敗';
}
}
パターン2: 複数のAPIを順番に呼び出す
// ❌ 間違い(順番が保証されない)
async function loadData() {
const user = fetchUser();
const posts = fetchPosts();
const comments = fetchComments();
return { user, posts, comments };
}
// ✅ 正しい(順次実行)
async function loadData() {
const user = await fetchUser();
const posts = await fetchPosts();
const comments = await fetchComments();
return { user, posts, comments };
}
// ✅ 正しい(並列実行 - より効率的)
async function loadData() {
const [user, posts, comments] = await Promise.all([
fetchUser(),
fetchPosts(),
fetchComments()
]);
return { user, posts, comments };
}
パターン3: エラーハンドリング
// ❌ 間違い(エラーがキャッチされない)
async function saveData(data) {
const response = await fetch('/api/save', {
method: 'POST',
body: JSON.stringify(data)
});
return response.json();
}
// ✅ 正しい(エラーハンドリングあり)
async function saveData(data) {
try {
const response = await fetch('/api/save', {
method: 'POST',
body: JSON.stringify(data)
});
if (!response.ok) {
throw new Error(`HTTP error! status: ${response.status}`);
}
return await response.json();
} catch (error) {
console.error('データの保存に失敗しました:', error);
throw error; // 呼び出し元にエラーを伝播
}
}
まとめ
async/awaitで混乱する主な原因は、Promiseの理解が不十分なことです。
覚えておくべき3つのポイント
-
awaitを忘れない - Promiseを返す処理には必ず
awaitを付ける - try-catchを忘れない - エラーハンドリングを必ず実装する
-
実行順序を意識する -
awaitがないと順番が保証されない
チェックリスト
コードを書いたら、以下のチェックリストで確認しましょう。
-
すべてのPromiseを返す処理に
awaitを付けているか? -
非同期関数を呼び出す側でも
awaitを付けているか? - エラーハンドリング(try-catch)を実装しているか?
- 実行順序が正しいか?(順次実行 or 並列実行)
もっと学びたい方へ
この記事でasync/awaitの基礎を理解できた方は、次のステップとして実践的な学習を進めてみませんか?
LTechでは、JavaScriptやNode.jsの非同期処理を実際にコードを書きながら学べるカリキュラムを提供しています。
LTechで学べること
- JavaScript基礎講座 - JavaScriptの基礎構文を段階的に学習
- Node.js講座 - Promise、async/awaitを段階的に学習
- Express.js講座 - 非同期処理を使ったWebアプリケーション開発
- 実践的な演習 - コードエディタで実際に手を動かしながら学習
- 理解度チェック - クイズで知識を定着
すべてのコースで、初心者でも理解しやすい説明と実際に動かせるコード例を用意しています。
この記事が、async/awaitで困っている方の助けになれば幸いです。質問やフィードバックがあれば、コメントでお知らせください!