3秒でわかる
処理が終わるのを待ってから次の行へ進む実行のしかた。書いた順にそのまま動くので読みやすい反面、待ち時間の分だけ全体が止まります。
もう少し詳しく
どういうものか
同期とは、ある処理の結果が出るまでその場で待ち、終わってから次の行へ進む実行のしかたです。上から下へ書いた順番どおりに動くため、コードの見た目と実際の動作が一致します。
なお「同期」という語は、複数の端末やサーバーでデータを一致させる意味でも使われます。ここで扱うのは実行順のほうの同期です。文章にどちらの意味で出てきたかは、対義語が「非同期」なら実行順、対義語が「バックアップ」や「片方向コピー」ならデータ側、と見分けられます。
なぜ必要か
同期のほうが素直に書けます。取得したデータをそのまま次の行で使えるので、途中結果を受け渡す仕組みを自分で用意しなくて済みます。
問題は待ち時間です。ネットワーク越しの通信は数十ミリ秒から数秒かかり、その間 CPU は何もしていません。1 件ずつ同期でリクエストを投げると、件数に比例して時間が伸びます。ブラウザの場合はさらに深刻で、画面の描画やクリックの反応まで止まります。
具体例
同じ 3 件の取得を、同期的に待つ場合と並行に走らせる場合で比べます。
// 1件ずつ待つ。合計は3件分の時間がかかる
const a = await <a href="/glossary/fetch-api" class="text-primary font-medium underline underline-offset-2 hover:text-primary-dark">fetch</a>("/api/1").then((r) => r.json());
const b = await fetch("/api/2").then((r) => r.json());
const c = await fetch("/api/3").then((r) => r.json());
// 3本同時に投げて待つ。合計はいちばん遅い1件分で済む
const [x, y, z] = await <a href="/glossary/promise-all" class="text-primary font-medium underline underline-offset-2 hover:text-primary-dark">Promise.all</a>([
fetch("/api/1").then((r) => r.json()),
fetch("/api/2").then((r) => r.json()),
fetch("/api/3").then((r) => r.json()),
]);つまずきやすいところ
await を見て「同期処理だ」と考えるのはよくある誤解です。await は非同期処理を同期っぽい見た目で書くための構文で、待っている間は他の処理が動きます。
もうひとつは、ループの中に await を書いて全件を直列に待ってしまうケースです。100 件のうち互いに依存しない処理なら、まとめて投げてから待つほうが桁で速くなります。逆に、前の結果を次のリクエストに渡す必要があるときは直列にするしかありません。依存があるかどうかで判断します。
似た用語との違い
| 語 | 意味 | 待ち方 |
|---|---|---|
| 同期 | 結果が出るまで進まない | その場で止まる |
| 非同期 | 結果は後で受け取る | 止まらず先へ進む |
| ブロッキング | 呼び出し元のスレッドを止める | スレッドごと止まる |
| ノンブロッキング | すぐ制御が戻る | 完了は別途通知される |
覚え方
「同期は待つ、非同期は預ける」と覚えると混ざりません。