TL

同期処理と非同期処理

結果を待ってから次に進むのが同期、待たずに先へ進んで完了は後で受け取るのが非同期。通信やファイル読み書きなど“待ち時間”の扱い方の違い。

中級非同期async/awaitイベントループ最終更新: 2026-06-04
TL;DR要点だけ先に
  • 1.同期は「終わるまで待つ(ブロッキング)」、非同期は「待たずに先へ進み、完了は後で受け取る(ノンブロッキング)」。
  • 2.書き方は コールバック → Promise → async/await と進化。async/await は非同期を“同期っぽく”読み書きするための糖衣。
  • 3.非同期は速くなる魔法ではない。CPUを使う重い計算ではなく、通信・ファイル・DBなど“待ち”が長いI/Oで効く。

なぜ非同期が必要?

プログラムには2種類の「重い処理」があります。

  • CPUバウンド:計算そのものが重い(画像処理、暗号化、巨大なソート)
  • I/Oバウンド:計算は軽いが待ちが長い(ネットワーク通信、ファイル読み書き、DB問い合わせ)

ネットワークの応答は数十〜数百ミリ秒。その間CPUはほぼ遊んでいます。同期で書くと、この「待ち」の間プログラム全体が止まります。とくにブラウザでは、待っている間画面が固まりクリックも効かなくなる。非同期は、この待ち時間に他の仕事を進めるための仕組みです。

// 同期(イメージ):応答が来るまでここで全部止まる
const data = fetchSync('/api/user'); // ← 200ms 待つ間、何もできない
console.log(data);
console.log('次の処理');             // 待ち終わってからやっと動く

ブロッキング vs ノンブロッキング

観点同期(ブロッキング)非同期(ノンブロッキング)
次の行へ結果が出るまで進まない待たずにすぐ進む
待ち時間その場で立ち止まる他の処理を進められる
結果の受け取り戻り値でそのままコールバック/Promise で後から
コードの見た目上から素直に読める工夫しないと追いづらい
向く処理軽い処理・順番が大事な処理通信・ファイル・DB など I/O 待ち

書き方の進化:コールバック → Promise → async/await

非同期の「終わったら受け取る」をどう書くか、JavaScript では3世代で進化しました。

1. コールバック

「終わったら呼んでね」と関数を渡す方式。素直ですが、待ちが連鎖すると右へ右へとネストが深くなり、いわゆるコールバック地獄になります。

getUser(id, (err, user) => {
  if (err) return handle(err);
  getPosts(user, (err, posts) => {      // どんどん深くなる
    if (err) return handle(err);
    getComments(posts, (err, comments) => {
      // ...ネストが続く
    });
  });
});

2. Promise

いずれ値が入る箱」を返す方式。.then() で繋ぎ、.catch() でエラーをまとめて受けられます。ネストが横→縦のチェーンになり、見通しが改善します。

getUser(id)
  .then(user => getPosts(user))
  .then(posts => getComments(posts))
  .then(comments => render(comments))
  .catch(handle); // どこで失敗してもここに集約

Promise は3つの状態を持ちます:pending(待機)→ fulfilled(成功)/ rejected(失敗)。一度決まると変わりません。

3. async/await

Promise を同期コードのように書ける糖衣構文await は「この Promise が解決するまで“この関数の中だけ”待つ」という意味で、見た目は同期、中身は非同期です。エラーは普通の try/catch で扱えます。

async function show(id) {
  try {
    const user = await getUser(id);
    const posts = await getPosts(user);
    const comments = await getComments(posts);
    render(comments);
  } catch (e) {
    handle(e);
  }
}
async 関数は必ず Promise を返す

await が使えるのは async 関数の中だけ。そして async 関数は、中で何を return しても結果は Promise に包まれて返ります。呼び出し側はさらに await するか .then() で受け取ります。await を付け忘れると、値ではなく「Promise そのもの」を掴んでしまうのが定番のミスです。

並行(concurrent)と並列(parallel)は別物

ここは混同しやすい最重要ポイント。

  • 並行(concurrency):複数の作業を切り替えながらこなす。同時に動いている“ように見える”。担当は1人でも成立する。
  • 並列(parallelism):複数の作業を物理的に同時に動かす。CPUのコアが複数必要。
観点並行(concurrency)並列(parallelism)
意味作業を切り替えて捌く本当に同時に動かす
たとえ1人が複数の鍋を交互に見るコックが複数いて別々の鍋
必要なもの1コアでも可複数コア(またはプロセス)
JSの典型イベントループによる非同期Web Worker / 別プロセス
非同期=並列ではない

JavaScript の非同期は基本「1本のスレッドで並行」です。await で待っている間に他の処理を進めますが、JS コード自体が同時に2つ走るわけではありません。CPUを食う重い計算を async にしても、その計算が走っている間はやはりスレッドが塞がります。非同期は“待ち”を有効活用する仕組みで、計算を速くする並列化とは別物です。

イベントループ:1本のスレッドで“待たずに”捌く仕組み

「シングルスレッドなのに、なぜ待たずに次へ進めるのか?」の答えがイベントループです。流れはこうです。

  1. 同期コードはコールスタックで上から順に実行される。
  2. fetch などの非同期処理(I/O)は、実行環境(ブラウザ/Node.js)側に依頼して、スタックからは即座に抜ける。
  3. 依頼した処理が完了すると、その続き(コールバック)がキューに積まれる。
  4. イベントループが「スタックが空になった」のを見計らって、キューの続きをスタックへ戻す。

つまり「待つ」のは JS のスレッドではなく環境側。だから1本のスレッドでも固まらずに済みます。

console.log('1');
setTimeout(() => console.log('2'), 0); // 0ミリ秒でも“後回し”
Promise.resolve().then(() => console.log('3'));
console.log('4');

// 出力順: 1 → 4 → 3 → 2
なぜ 1→4→3→2 ?

まず同期コード1,4)が全部終わる。次に Promise の続き(マイクロタスク3)が優先され、最後に setTimeoutマクロタスク2)。setTimeout(…, 0) は「最速」ではなく「同期処理が終わってから」の意味だと分かると、多くの“順番の謎”が解けます。

つまずきポイント

ループの中で逐次 await して遅くする

独立した非同期処理を for の中で1つずつ await すると、直列に積み上がって遅くなります(10件×200ms=2秒)。互いに依存しないなら Promise.all でまとめて並行に流すのが定石(最も遅い1件ぶん=約200msで済む)。

// ❌ 直列:合計 = 各処理の合計時間
for (const id of ids) {
  results.push(await fetchUser(id));
}

// ✅ 並行:合計 ≒ 一番遅い1件
const results = await Promise.all(ids.map(id => fetchUser(id)));
エラーは“握りつぶさない”

非同期のエラーは、await なら try/catch、Promise チェーンなら .catch() で必ず受ける。await を付け忘れた Promise や、.catch() の無いチェーンで失敗すると、どこにも出ずに静かに消える(unhandled rejection)ことがあります。「動いてるのに結果が来ない」系の不具合の温床です。

いつ非同期にすべき?

判断はシンプルで、「待ちが長いか/止まると困るか」です。

  • 非同期が効く:ネットワーク通信、ファイル/DBアクセス、外部API、タイマー —— つまりI/Oバウンド
  • 非同期にしても速くならない:純粋な計算が重いCPUバウンド。これは並列化(Web Worker や別プロセス)やアルゴリズム改善の領域。
  • 順番が本質的に大事で待ち時間も短いなら、無理に非同期化せず同期のまま素直に書く方が読みやすい。
まとめ

非同期は「速くする魔法」ではなく「待ち時間を無駄にしない仕組み」。async/await同期のように読めるコードを書きつつ、独立処理は Promise.all で並行に。そして「並行(捌き方)」と「並列(同時実行)」は別物——ここを押さえれば、非同期の8割は理解できたも同然です。エラー処理は例外とエラーハンドリングも合わせてどうぞ。

プログラミング Article

同期処理と非同期処理を実務で読む

TL;DRは入口です。実際に選ぶ・使う段階では、何を解決するか、何と比較するか、導入後にどこで詰まるかまで見る必要があります。

解決すること

非同期

比較で見る軸

難易度: intermediate / カテゴリ: プログラミング / タグ数: 3

導入後に効く点

書き方は コールバック → Promise → async/await と進化。async/await は非同期を“同期っぽく”読み書きするための糖衣。

先に潰すリスク

用語だけ覚えても、設計・実装・運用でどこに効くかを確認しないと判断を誤る。

数字・仕様の読み方
難易度
intermediate
カテゴリ
プログラミング
タグ数
3

判断チェックリスト

  • 自社の用途が「非同期 / async/await」に近いか確認する。
  • 強みである「同期は「終わるまで待つ(ブロッキング)」、非同期は「待たずに先へ進み、完了は後で受け取る(ノンブロッキング)」。」が本当に評価軸になるか確認する。
  • 注意点の「用語だけ覚えても、設計・実装・運用でどこに効くかを確認しないと判断を誤る。」を運用で吸収できるか確認する。
  • 公開値や仕様値は、対象プラン・対象機種・対象リージョンまで確認する。
  • 既存システム、ID、ネットワーク、監視、バックアップとの接続方法を先に洗い出す。
  • 小さく試してから、本番移行、権限設計、障害時手順、コスト監視を決める。

次に確認する観点

非同期async/awaitイベントループ非同期async/awaitイベントループ
参考: 公式情報