TechBlog
記事一覧に戻る
JavaScriptの非同期処理を完全理解
2026年2月14日
JavaScript

JavaScriptの非同期処理を完全理解

JavaScriptの非同期処理について、Callback、Promise、async/awaitの違いと使い方を実例とともに解説します。

1. そもそも「非同期処理」って何?

JavaScriptは基本的に 1つの処理を順番に実行する(同期的) 言語です。

ただし、通信(API)・タイマー・ファイル読み込みなど 時間がかかる処理 を「待っている間」に他の処理が止まると困ります。

そこで登場するのが 非同期処理です。

  • 同期:終わるまで次に進まない
  • 非同期:終わるのを待たずに次に進む(終わったら通知してくる)

2. 非同期っぽさを体験する(setTimeout)

まずは最小の例です。


console.log("A");

setTimeout(() => {
  console.log("B(あとから)");
}, 1000);

console.log("C");

出力はこうなります:

A
C
B(あとから)

setTimeout は「1秒後に実行予約」するだけなので、処理は止まらず C が先に出ます。


3. Callback(コールバック)で非同期を扱う

Callbackは「終わったら呼ぶ関数」を渡すスタイルです。

例:疑似API(成功/失敗あり)

Zfunction fetchUserCallback(userId, callback) {
  setTimeout(() => {
    if (!userId) {
      callback(new Error("userIdがありません"), null);
      return;
    }
    callback(null, { id: userId, name: "Taro" });
  }, 500);
}

fetchUserCallback(1, (err, user) => {
  if (err) {
    console.error("失敗:", err.message);
    return;
  }
  console.log("成功:", user);
});

Callbackのつらさ:ネストが深くなりがち(コールバック地獄)

fetchUserCallback(1, (err, user) => {
  if (err) return console.error(err);

  fetchUserCallback(user.id + 1, (err2, user2) => {
    if (err2) return console.error(err2);

    fetchUserCallback(user2.id + 1, (err3, user3) => {
      if (err3) return console.error(err3);

      console.log("3人取得:", user, user2, user3);
    });
  });
});

問題点

  • ネストが増えて読みにくい
  • エラーハンドリングが散らばる
  • 処理の流れを追いづらい

4. Promiseで改善する

Promiseは「未来に結果が返ってくる」ことを表現する仕組みです。

  • resolve:成功
  • reject:失敗

Callback版をPromise版にする

function fetchUserPromise(userId) {
  return new Promise((resolve, reject) => {
    setTimeout(() => {
      if (!userId) {
        reject(new Error("userIdがありません"));
        return;
      }
      resolve({ id: userId, name: "Taro" });
    }, 500);
  });
}

then/catchでつなげられる

fetchUserPromise(1)
  .then((user) => {
    console.log("1人目:", user);
    return fetchUserPromise(user.id + 1);
  })
  .then((user2) => {
    console.log("2人目:", user2);
    return fetchUserPromise(user2.id + 1);
  })
  .then((user3) => {
    console.log("3人目:", user3);
  })
  .catch((err) => {
    console.error("どこかで失敗:", err.message);
  });

Callbackより良い点

  • ネストが浅くなる
  • エラーは最後にまとめてcatchできる
  • “処理をつなぐ”のが自然

5. async/awaitでさらに読みやすくする

async/await は Promise を 同期処理っぽい見た目で書ける構文です。

  • async を付けた関数は必ずPromiseを返す
  • await は Promise の完了を待って結果を取り出す

Promiseをasync/awaitで書き換え

async function main() {
  try {
    const user1 = await fetchUserPromise(1);
    console.log("1人目:", user1);

    const user2 = await fetchUserPromise(user1.id + 1);
    console.log("2人目:", user2);

    const user3 = await fetchUserPromise(user2.id + 1);
    console.log("3人目:", user3);
  } catch (err) {
    console.error("失敗:", err.message);
  }
}

main();

一気に読みやすくなるポイント

  • 上から下へ順番に読める
  • try/catchでエラー処理をまとめられる
  • ロジックが「普通の手続き処理」に近い

まとめ

非同期処理は「時間がかかる処理を待つ/待たない」を扱う仕組みです。

  • Callback:動くけどネストが増えがち
  • Promise:チェーンで書けて見通しが良い
  • async/await:Promiseを同期っぽく書けて最も読みやすい