なぜJavaScriptの非同期処理でコードが思い通りに動かないのか?
「なんでconsole.logの結果がundefinedなの?」
「さっきまで動いてたのに、急に動かなくなった...」
こんにちは、とまだです。
JavaScript の非同期処理で、こんな経験ありませんか?
実は私、初めてのWebアプリ開発で、APIからデータを取得する処理を書いた時、かなり悩みました。
// これ、私が最初に書いたコード
let userData;
fetch('/api/user')
.then(response => response.json())
.then(data => userData = data);
console.log(userData); // undefined...なんで!?
「コードは上から順番に実行されるはず」という思い込みが、見事に裏切られた瞬間でした。
でも大丈夫です。
今回は現役のエンジニア、そして元プログラミングスクール講師としての経験から、JavaScript非同期処理について解説します。
なぜ非同期処理が必要なのか(待ってたら何もできない)
簡単に言うと、非同期処理とは「待たない」プログラミングです。
レストランを想像してみてください。
もし「同期処理」だけの世界だったら:
- お客様Aの注文を聞く
- 料理を作る(20分待つ)
- 料理を提供する
- やっとお客様Bの注文を聞ける
こんな店、潰れますよね。
私がバイトしていたレストランも、最初は「一人ずつ対応」していて大混乱でした。
だから現実のレストランは「非同期」で動きます:
- ウェイターAがお客様Aの注文を聞く
- 同時にウェイターBがお客様Bに水を運ぶ
- 厨房では複数の料理を並行して調理
- できた料理から順番に提供
JavaScriptも同じです。APIからデータを取得している間も、ユーザーはボタンをクリックできるし、アニメーションも動き続けます。
非同期処理でつまずく3つのポイント
1. 実行順序が予測できない
console.log('1番目');
setTimeout(() => {
console.log('2番目?');
}, 0);
console.log('3番目?');
// 結果:
// 1番目
// 3番目
// 2番目? ← えっ?
私も最初「setTimeoutが0秒なのに、なんで後に実行されるの?」って混乱しました。
2. エラーが取れない
try {
setTimeout(() => {
throw new Error('エラー!');
}, 1000);
} catch (e) {
console.log('キャッチできない...'); // これは実行されない
}
非同期処理のエラーは、普通のtry-catchでは捕まえられません。これで何度バグを生み出したことか...。
3. Promise地獄
// こんなコード書いたことありません?
getData()
.then(data => {
return processData(data)
.then(result => {
return saveData(result)
.then(saved => {
return notifyUser(saved)
.then(() => {
// もう何がなんだか...
});
});
});
});
ネストが深くなりすぎて、自分でも読めなくなります。
5つのステップで非同期処理をマスター
ステップ1:コールバックを理解する
コールバックは「終わったら呼んでね」という仕組みです。
// レストランで注文する感じ
function orderCoffee(callback) {
console.log('コーヒーを注文しました');
setTimeout(() => {
console.log('コーヒーができました');
callback('ホットコーヒー');
}, 2000);
}
orderCoffee((coffee) => {
console.log(`${coffee}をいただきます`);
});
ステップ2:Promiseで約束を管理
Promiseは「必ず結果を返す約束」です。
// 注文票みたいなもの
function orderFood() {
return new Promise((resolve, reject) => {
const isAvailable = Math.random() > 0.5;
setTimeout(() => {
if (isAvailable) {
resolve('ハンバーグ定食');
} else {
reject('申し訳ございません、売り切れです');
}
}, 1000);
});
}
orderFood()
.then(food => console.log(`${food}が来ました!`))
.catch(error => console.log(error));
ステップ3:async/awaitで自然に書く
これが一番わかりやすい書き方です。
async function lunchTime() {
try {
console.log('レストランに入りました');
const food = await orderFood(); // 料理が来るまで待つ
console.log(`${food}をいただきます`);
const dessert = await orderDessert(); // デザートも待つ
console.log(`${dessert}も美味しい`);
console.log('ごちそうさまでした');
} catch (error) {
console.log('注文エラー:', error);
}
}
見てください。上から下に自然に読めますよね。
ステップ4:並列処理を活用
複数の料理を同時に注文できます。
async function orderMultiple() {
// 同時に3つ注文(効率的!)
const [coffee, sandwich, salad] = await Promise.all([
orderCoffee(),
orderSandwich(),
orderSalad()
]);
console.log('全部揃いました!');
}
ステップ5:エラーハンドリングを確実に
// よくある間違い
async function badExample() {
const data = await fetchData(); // エラーが起きたらクラッシュ
}
// 正しい書き方
async function goodExample() {
try {
const data = await fetchData();
return data;
} catch (error) {
console.error('データ取得エラー:', error);
// デフォルト値を返すか、エラーを再スロー
return { default: true };
}
}
実際にやらかした3つの失敗談
失敗1:awaitを忘れる
// 私がやらかしたコード
async function getUserData() {
const response = fetch('/api/user'); // awaitを忘れた!
return response.json(); // エラー:responseはPromiseです
}
これでしばらく悩みました。「なんでjsonメソッドがないの?」って。
同僚に聞いたら「awaitつけ忘れてるよ」って一言。恥ずかしかった...
失敗2:forEachでawaitが使えない
// これは動かない
const ids = [1, 2, 3];
ids.forEach(async (id) => {
await processId(id); // forEachの中でawaitは効かない
});
// 正しくはfor...ofを使う
for (const id of ids) {
await processId(id);
}
「なんで順番に処理されないの?」ってずっと悩みました。
失敗3:Promiseの連鎖を忘れる
// returnを忘れると連鎖が切れる
fetchData()
.then(data => {
processData(data); // returnを忘れた!
})
.then(result => {
console.log(result); // undefined
});
実践的なサンプル:APIを使ったユーザー情報取得
最後に、実際によく使うパターンを紹介します。
// ユーザー情報を取得して表示する
async function displayUserInfo(userId) {
// ローディング表示
showLoading();
try {
// APIからデータ取得
const response = await fetch(`/api/users/${userId}`);
// レスポンスチェック
if (!response.ok) {
throw new Error('ユーザーが見つかりません');
}
// JSONに変換
const user = await response.json();
// UIを更新
updateUI(user);
} catch (error) {
// エラー表示
showError(error.message);
} finally {
// ローディング非表示
hideLoading();
}
}
このパターンを覚えておけば、大抵のAPI通信は実装できます。
まとめ
JavaScript の非同期処理は「待たないプログラミング」です。
重要なポイント:
- コールバック → 終わったら呼んでね
- Promise → 必ず結果を返す約束
- async/await → 自然に書ける魔法
- エラーハンドリング → try-catchを忘れずに
- 並列処理 → Promise.allで効率化
私も最初は「なんでundefinedなの?」「なんで順番通りに動かないの?」って悩みまくりました。
でも、レストランの仕組みと同じだと理解してから、すんなり書けるようになったんです。
まずはasync/awaitから始めてみてください。きっと「あ、意外と簡単かも」って思えるはずです。
私が最初に悩んだことも、今ではすぐに書けます。それも最初につまずいたおかげで、しっかり理解できたから。
だから今悩んでいるあなたも、大丈夫。
つまずくのは成長の証拠です。
あなたも必ず非同期処理をマスターできます。一緒に頑張りましょう!
著者について

とまだ
フルスタックエンジニア
Learning Next の創設者。Ruby on Rails と React を中心に、プログラミング教育に情熱を注いでいます。初心者が楽しく学べる環境作りを目指しています。
著者の詳細を見る →