非同期処理とは?なぜ必要なのか

🌀 同期処理と非同期処理の違い

JavaScriptの処理は 基本的に「上から順に1行ずつ実行される(同期処理)」 が基本です。

console.log("A");
console.log("B");
console.log("C");
// 結果: A → B → C

でも、以下のように時間のかかる処理(例:サーバーとの通信やタイマーなど)を扱う場合、 すべての処理が終わるまで 待ってしまう と、UIがフリーズしてしまうことがあります。

😱 問題:時間のかかる処理があると…

console.log("A");
setTimeout(() => {
  console.log("B"); // ← ここに3秒かかるとする
}, 3000);
console.log("C");

// 結果: A → C → (3秒後)B

ここでの setTimeout は代表的な「非同期処理」です。

JavaScriptは「待っている間に、次の処理を先に進める」ことができます。 これを 非同期処理(Asynchronous processing) と呼びます。

✅ なぜ非同期処理が重要なのか?

  • ボタンをクリックしたときに通信するようなWebアプリでは「待ってる間にフリーズする」ようなことがあっては困る。
  • スマホアプリなどでも「バックグラウンドで何かを読み込みながら、ユーザーは操作を続けられる」必要がある。
  • ゲームでは「効果音や画像の読み込みが遅くても、ゲームは進行できる」ことが大切。

これらを実現するために、JavaScriptには非同期処理のしくみがしっかりと用意されています。

user icon

何となく使ってましたが確かに、setTimeoutは非同期処理ですね。 指定時間待った後、実行するよう処理は、タイトルが出てすぐにBGMを流すと違和感があるので、フェードインした後ぐらいのタイミングで流した方が違和感が少なくなりますし。

ただ、便利な反面、メイン処理との整合性が取り辛いのもありますね…。


ai icon

その通りです!🎯

setTimeout や setInterval のような 非同期処理は、便利だけど「タイミングを制御しにくい」「処理の順序が複雑になる」といった 整合性の問題 を引き起こすことがあります。

まさに、以下のようなことを感じたことがあるかもしれません:

❗ よくある非同期処理の困りごと

  • 「Aの処理が終わってからBをしたいのに、先にCが実行されてしまった…」
  • 「setTimeoutで調整しても、処理順がズレる時がある」
  • 「コードがネストして読みにくくなる」

こういった課題を解決するために登場したのが、次章で紹介する コールバック関数 → Promise → async/await という進化の流れなんです。

コールバック関数とその限界

🔁 コールバック関数とは? 「処理が終わった後に、呼び出す関数」のことを コールバック関数 と呼びます。

function fetchData(callback) {
  setTimeout(() => {
    const data = "データ取得完了!";
    callback(data); // ← 処理が終わったら、callbackを実行
  }, 2000);
}

fetchData(function (result) {
  console.log(result);
});

🟢 このコードの流れ:

  1. fetchData が呼ばれる
  2. 2秒後に data ができる
  3. callback(data) により、ログ出力が呼ばれる

🌀 コールバック地獄(Callback Hell)

コールバックが便利でも、ネストが深くなると読みにくくなる 問題があります。

doSomething(function(result1) {
  doSomethingElse(result1, function(result2) {
    doThirdThing(result2, function(result3) {
      console.log('完了', result3);
    });
  });
});

これがいわゆる「コールバック地獄」と呼ばれる状態です。 読みづらく、バグの温床にもなります。

この課題を解決するために、Promise という新しい書き方が登場しました。 次の章では、その Promise の基本について解説していきます。

user icon

コールバック処理は、スマートフォンアプリを作ってた時にJavaで書いたことがあります。 検索中の待ち時間に、フリーズしてしまうので、処理が終わった後、画面に結果を出力する際にコールバックを使った記憶があります。

もう忘れてしまいましたが…。

JavaScrptにも同様の仕組みがあるんですね。

次の「Promiseとは何か(基礎と例)」お願いします。


ai icon
まさにその通りです!
Java の非同期処理(特に Android)でも AsyncTask や Handler, Runnable, Callback を使ってましたよね。
JavaScriptにも、それと同じように **「処理が終わったら続きをやる」**という構造が必要で、そこで登場したのが Promise(約束) です。

Promiseとは何か(基礎と例)

🔷 Promiseの基本概念

Promise は、「今は終わってないけど、いつか終わる処理を表すオブジェクト」です。

  • 成功(resolve)
  • 失敗(reject)

という 2つの未来の結果 のどちらかになることを「約束(Promise)」してくれるんです。

📦 Promiseの構文(基本形)

const promise = new Promise((resolve, reject) => {
  // 非同期処理を書く
  const success = true;

  if (success) {
    resolve("成功しました!");
  } else {
    reject("失敗しました…");
  }
});

promise
  .then(result => {
    console.log("成功:", result);
  })
  .catch(error => {
    console.error("エラー:", error);
  });

🧠 これで何が嬉しいの? Promiseを使うと:

  • .then() で「成功したときの処理」
  • .catch() で「失敗したときの処理」

を分けて書けるようになるので、ネストが浅く、読みやすいコード になります。

⏱ 実例:2秒後に成功するPromise

function waitTwoSeconds() {
  return new Promise((resolve) => {
    setTimeout(() => {
      resolve("2秒経ちました!");
    }, 2000);
  });
}

waitTwoSeconds()
  .then(message => {
    console.log(message); // → 「2秒経ちました!」
  });

📝 補足:Promiseの3つの状態

  1. pending(保留中)
  2. fulfilled(成功:resolve)
  3. rejected(失敗:reject)

Promiseが出てきたおかげで、非同期処理を直線的に書ける ようになってきました。 でも .then().then().catch() と書くのもやっぱり面倒ですよね。

そこで登場するのが、次の主役…

✅ async/await の出番!

次は、現代の非同期処理の王道「async/await」について解説します。

user icon

ありがとうございます。

コールバックは分かるのですが、Promiseはちょっと難しいというか、具体的に活用する場面が思い浮かばないのもあり、理解が追い付かないですね…。

また分からない時お聞きするかもしれませんが、とりあえず、次の「async / await の登場とメリット」お願いします。


ai icon
ありがとうございます、丁寧に受け取っていただいて嬉しいです。
Promiseがまだ少しピンとこなくても大丈夫です。
実は、**次に出てくる async / await がPromiseを“もっと分かりやすく使う書き方”**なので、そこで理解が深まることがよくあります。

async / await の登場とメリット

🎬 なぜ async / await が生まれたのか? JavaScriptの非同期処理は、昔は:

  • コールバック地獄(callback hell)
  • .then().then().catch() でPromiseが複雑に連なる

といった問題がありました。

もっと読みやすく!エラーも try/catch で扱いたい!

そんな声から生まれたのが async / await です。

🔧 async / await の基本文法

async function sample() {
  const result = await 非同期処理(); // 終わるまで待つ
  console.log(result); // 結果を使う
}

🎉 例:Promiseをawaitで書き直す Promiseを使った書き方:

waitTwoSeconds().then(result => {
  console.log(result);
});

async / await を使った書き方:

async function run() {
  const result = await waitTwoSeconds();
  console.log(result);
}

run();

🔍 → これだけで 処理の流れが自然な順番で書ける ようになります!

🔒 エラーハンドリングも自然に

async function run() {
  try {
    const data = await fetchSomething();
    console.log(data);
  } catch (error) {
    console.error("エラー:", error);
  }
}

🧠 どんな時に使うの?

  • ファイル読み込み
  • APIからデータ取得(fetchなど)
  • 画像や音声のロード
  • ゲームやアニメーションの制御(時間差があるもの)
  • BGM再生のタイミング制御など

といった、「何かを待ってから次に進めたい」すべての場面に使えます。

✅ メリットまとめ

項目 内容
✅ 読みやすい コードが直列に書ける(まるで同期処理)
✅ エラー処理 try/catchで囲める
✅ バグが減る await で「待つ」が明示的になる
✅ Promiseより分かりやすい .then() を連ねなくて済む

次の章では、実際に async / await を使ったサンプルコードの構築例 を紹介します。

進めてもよいでしょうか? (もしくは、Promiseのところに戻ってじっくりでもOKです)

user icon

ありがとうございます。

async / await を使った書き方はめちゃくちゃわかりやすいですね…。

非同期と言えば「async」という単語はもう定番ですね。 Javaでもお手本を真似て何となく書いてた記憶があります。

昨日のゲーム処理の中で以下のような構文を何となく書いてましたが、さっぱりわからなかったですが、解説を聞いて少しわかりました。

  // 画像読み込みを待つ
  await new Promise((resolve) => {
    img.onload = resolve;
  });

次の「実際に async / await を使ったサンプルコードの構築例」お願いします。


ai icon
こちらこそありがとうございます!
そうです、その await new Promise((resolve) => { … }) の書き方は、非同期処理を自作で“待つ”ときによく使われる定番パターンです!実はとても本質的なコードなんですよ。

実践:非同期処理を使ったサンプルコード(fetchなど)

🎮 例題:「画像を読み込んで表示 → 2秒後にメッセージを表示」 目的:

  • 画像の読み込み という非同期処理
  • 一定時間の待機(2秒)
  • その後にメッセージを表示する

これを すべて async / await でシンプルに実装 してみます。

🧩 ステップ1:画像の読み込み関数(Promise化)

function loadImage(url) {
  return new Promise((resolve, reject) => {
    const img = new Image();
    img.src = url;

    img.onload = () => resolve(img);
    img.onerror = () => reject("画像の読み込みに失敗しました");
  });
}

⏳ ステップ2:待機関数(指定ミリ秒待つ)

function delay(ms) {
  return new Promise((resolve) => setTimeout(resolve, ms));
}

🚀 ステップ3:非同期処理を組み合わせて実行

async function main() {
  try {
    // 画像読み込みを待つ
    const img = await loadImage("sample.jpg");
    document.body.appendChild(img); // 表示

    // 2秒待つ
    await delay(2000);

    // メッセージ表示
    const msg = document.createElement("p");
    msg.textContent = "画像の読み込みが完了しました!";
    document.body.appendChild(msg);
  } catch (error) {
    console.error(error);
  }
}

// 実行
main();

📦 全体の流れまとめ

  1. loadImage() が画像の読み込みをPromiseで処理
  2. await で画像の読み込み完了を“待つ”
  3. delay() で2秒間“待つ”
  4. メッセージを表示

👀 補足:実行のポイント

  • HTML上で script タグで読み込めばすぐ動きます
  • ブラウザのデベロッパーツールでエラーが出ていないか確認
  • 同じようにBGM再生やアニメーションの連続処理にも応用できます

🔄 これが実際のゲームコードにどう活きるか? 例えば以下のような流れも、asyncで表現できます:

async function gameStart() {
  await fadeInTitle();      // タイトルフェードイン
  await delay(1500);        // 少し待つ
  await playBGM();          // BGM再生
  await showCards();        // カード表示
}

直感的で、読みやすく、バグも減ります!

このサンプルが終わったら、次は応用編として「複数の非同期処理を並列で走らせる」 Promise.all() や for await の話にも進めますが、一旦ここで質問や不明点などあれば、どうぞ! (このコードも実際に試してみると理解が深まります)

user icon

ありがとうございます。

理屈とか仕組みは何となく分かり、使いこなせると凄く便利そうですが、ちょっと敷居が高く感じて難しいですね。

デザインパターンのような物で、基本構文を使いまわして使っていけばいいのかなと…。

まだ、実践的な使い方は難しい気がしますが、ゲーム開発をする上で避けられない処理なんでしょうね。

async / await を使ったサンプルコード

ai icon
ありがとうございます! ここからは応用編として「複数の非同期処理を並列で走らせる」テクニックを学んでいきましょう。

応用編:複数の非同期処理を並列で走らせる方法

✅ 目次

  • Promise.all():複数の非同期処理を並列実行してすべて完了を待つ
  • for await…of:非同期な配列を逐次処理する

🔷 1. Promise.all() の使い方と例

💡 概要:

  • 複数の非同期処理を同時に実行
  • すべてが完了するまで待ってから次に進む
  • 失敗したものが1つでもあれば、すべてが reject 扱いになる

🧪 例:3枚の画像を並列で読み込む

function loadImage(url) {
  return new Promise((resolve, reject) => {
    const img = new Image();
    img.src = url;
    img.onload = () => resolve(img);
    img.onerror = () => reject(`画像の読み込みに失敗: ${url}`);
  });
}

async function loadAllImages() {
  try {
    const imageUrls = ["img1.jpg", "img2.jpg", "img3.jpg"];

    // 並列に読み込みを開始
    const results = await Promise.all(imageUrls.map(loadImage));

    // 読み込み完了したら表示
    results.forEach((img) => document.body.appendChild(img));
  } catch (error) {
    console.error(error);
  }
}

loadAllImages();

📌 ポイント:

  • .map(loadImage) でURLごとに読み込み処理を作成
  • Promise.all([…]) に渡して同時に走らせる
  • すべて成功しないと await は進まない

🔷 2. for await…of の使い方と例

💡 概要:

  • 非同期イテレーションを順に処理
  • await をループの中で使える

🧪 例:順番に画像を読み込んで、1枚ずつ表示

async function loadImagesSequentially(imageUrls) {
  for await (const url of imageUrls) {
    const img = await loadImage(url);
    document.body.appendChild(img);
    await delay(1000); // 1秒待つ
  }
}

loadImagesSequentially(["img1.jpg", "img2.jpg", "img3.jpg"]);

📌 ポイント:

  • for await…of は ES2018 以降で使える構文
  • 画像を1枚ずつ表示+待機などに最適
  • ゲーム中の演出やチュートリアル、カード1枚ずつ展開などに応用可!

🧠 まとめ

処理方法 同時実行 処理順 失敗時の動作
Promise.all() 全部完了後まとめて 1つでも失敗したら全体が失敗
for await...of ✕(逐次) 順番に実行 個別に try/catch で対応可能

次は「リアルなゲーム開発シーンでこれらがどう使えるか」について、実践的な構成に落とし込んでいく話もできますよ!

また、不明な点があれば「その行だけ詳しく」なども可能です。 ご希望あれば続きますし、ここで記事として整形して出力もできます。

user icon

ありがとうございます。

「 Promise.all」なんて書き方も出来るんですね。
一度に複数のスレッドを走らせるような感じなのかなと…。

「for await 」なんて書き方も出来るとは…、目から鱗です。

とりあえず一度にやると理解が進まないので、ここで、止めておきます。

講習内容は、記事としてまとめたので、ほぼ自分用ですが、公開する予定です。

ありがとうございました。


ai icon

こちらこそ、最後まで真剣に取り組んでいただき本当にありがとうございました。
あなたのように一つずつ丁寧に理解して進もうとする姿勢は、エンジニアとしても、学び手としても素晴らしい資質です。

一度に複数のスレッドを走らせるような感じ
はい、それにかなり近いイメージです。JavaScriptでは厳密なマルチスレッドではないですが、「非同期に並列っぽく動かす」手法として Promise.all() は非常に実用的です。

そして:

記事としてまとめたので、ほぼ自分用ですが、公開する予定です。

その取り組みが、今の自分を未来の自分が助ける仕組みになっていくと思います。公開してくれることで、他の誰かにとってもきっと助けになります。

また「非同期処理のその後」や「Promise.race」など、気になった時にいつでも続きをやりましょう。
もちろん、コードの設計やバグの相談も歓迎です。

おつかれさまでした。
明日もまた、コードと共に生きる一日になりますように。☕✨