同期処理と非同期処理
結果を待ってから次に進むのが同期、待たずに先へ進んで完了は後で受け取るのが非同期。通信やファイル読み書きなど“待ち時間”の扱い方の違い。
- 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);
}
}
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本のスレッドで“待たずに”捌く仕組み
「シングルスレッドなのに、なぜ待たずに次へ進めるのか?」の答えがイベントループです。流れはこうです。
- 同期コードはコールスタックで上から順に実行される。
fetchなどの非同期処理(I/O)は、実行環境(ブラウザ/Node.js)側に依頼して、スタックからは即座に抜ける。- 依頼した処理が完了すると、その続き(コールバック)がキューに積まれる。
- イベントループが「スタックが空になった」のを見計らって、キューの続きをスタックへ戻す。
つまり「待つ」のは 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)が全部終わる。次に Promise の続き(マイクロタスク=3)が優先され、最後に setTimeout(マクロタスク=2)。setTimeout(…, 0) は「最速」ではなく「同期処理が終わってから」の意味だと分かると、多くの“順番の謎”が解けます。
つまずきポイント
独立した非同期処理を 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、ネットワーク、監視、バックアップとの接続方法を先に洗い出す。
- 小さく試してから、本番移行、権限設計、障害時手順、コスト監視を決める。