まず「なぜ非同期なんて面倒なものがあるのか」
いきなり本音から言うと、多くの人はこう感じます。
「同期処理だけで書けたらどれだけ楽か」
「非同期って急に難しくなるし、できれば避けたい」
実は JavaScript の世界でも、本音はかなり近いです。
それでも非同期が必要なのは、「同期のままだと、ユーザーにとって致命的に使いづらいアプリになってしまう」 からです。
ここが重要です。
非同期は「言語仕様が難しくしたいから」あるのではなく、
「ユーザー体験を壊さずに、時間のかかる処理を扱うための、ほぼ唯一の現実的な方法」 です。
JavaScript の前提条件:シングルスレッドの世界
一人で全部やる店員さんというイメージ
JavaScript(ブラウザ上)には、基本的に「メインスレッド」が 1 本しかありません。
この 1 本が、全部を担当します。
画面の描画
クリックやキー入力の処理
あなたの JavaScript コードの実行
つまり、「店員さんが一人しかいない店」 みたいな状態です。
この店員さんに、
レジ打ちも、品出しも、電話対応も、全部やってもらうイメージです。
同期処理だけだと何が起こるか
この店員さんが、お客さん A の対応中に「ちょっとそこに立って 5 分間何もしないで待っていて」と言われたらどうなるか。
その 5 分間、店員さんは他のお客さん B, C, D の対応を一切できません。
JavaScript でいうと、
サーバーの返事を待っている間
巨大なファイルの読み込みを待っている間
一定時間じっと待っている間
その時間、クリックもスクロールも反応しない、
「固まった」「落ちた?」と感じるアプリになります。
非同期が必要な一番の理由は、ここにあります。
「時間のかかる処理を待っている間も、他の仕事を続けたい」。
それを実現するために、非同期がどうしても必要になるのです。
ブロッキングの具体例:同期だけで書いた世界の地獄
重たい処理が UI を完全に止める例
次のようなコードをブラウザで実行することを考えてみます。
console.log("A: 開始");
const start = Date.now();
while (Date.now() - start < 5000) {
// 5秒間ひたすら何かしているつもり
}
console.log("B: 終了");
JavaScript実際にやってみると分かりますが、
” A: 開始 ” が出た後、5 秒間ブラウザがほぼフリーズします。
スクロールもカクカク、他のボタンを押しても反応がありません。
5 秒後、” B: 終了 ” が出て、ようやく操作が戻ってくる。
これは、メインスレッドが 5 秒間ずっとこの while に捕まっている からです。
この 5 秒間、イベントループは次のイベントを処理できず、
クリックや setTimeout のコールバックもずっと「待ちっぱなし」になります。
もしネットワーク通信を同期でやったら
ブラウザには「同期的な XHR(XMLHttpRequest)」という昔の機能があります。
これは「サーバーの返事が返ってくるまで、メインスレッドを完全に止める」という動きをします。
例えば(今は非推奨ですが):
const xhr = new XMLHttpRequest();
xhr.open("GET", "/data.json", false); // 第3引数 false が「同期通信」
xhr.send(); // 返事が返るまでここで完全に止まる
console.log("ここに来るまでUIも全部止まる");
JavaScriptこの間、ユーザーは何もできません。
回線が遅い、サーバーが重いといった原因で 3 秒、5 秒、10 秒かかったら、その時間はまるまる「固まった画面」を見せることになります。
だからこそ、この同期通信は今ほぼ全ての現場で禁止レベルです。
現実的ではありません。
ここが重要です。
時間の読めない処理(ネットワーク・I/O)を同期的に待つことは、ユーザー体験を崩壊させる。
これを避けるために、非同期という道具が必要になります。
非同期がある世界:待っている間も動き続ける
setTimeout を使って「待つ」を非同期にする
同じ「5 秒待つ」でも、次のように書いたらどうでしょう。
console.log("A: 開始");
setTimeout(() => {
console.log("B: 5秒後の処理");
}, 5000);
console.log("C: 他の処理を続ける");
JavaScript実行順は、
A: 開始
C: 他の処理を続ける
(5 秒後)
B: 5秒後の処理
になります。
この 5 秒間、UI は動きます。
スクロールもできるし、他のボタンも押せます。
理由は、
setTimeout に「5秒後にこれを実行して」と依頼した時点で、JavaScript は次に進める
待つのは JavaScript 本体ではなく、「タイマーを管理する Web API」側
5 秒経ったら「この関数を実行していいよ」とタスクキューに積まれ、イベントループが暇になったタイミングで実行
という構造だからです。
fetch を使って「通信」を非同期にする
今度はサーバー通信です。
console.log("1: リクエスト前");
fetch("/data.json")
.then((res) => res.json())
.then((data) => {
console.log("3: データ取得完了", data);
});
console.log("2: 他の処理");
JavaScript実行順は、
1: リクエスト前
2: 他の処理
(サーバーから返ってきたタイミングで)
3: データ取得完了
になります。
通信している間も、画面は固まりません。
ユーザーは入力を続けられるし、別ページに移ることもできます。
ここが重要です。
非同期を使うと、「遅い処理を待っている間もアプリが動き続ける」状態を作れる。
これが、現代の Web アプリには必須。
JavaScript 特有の「非同期が強く必要になる」事情
シングルスレッドなので、「止めたら全部止まる」
JavaScript のメインスレッドは一本だけです。
これは、他の多くのプログラミング言語と比べるとけっこう特殊です。
複数スレッドを簡単には持てない代わりに、
イベントループ+非同期 API で「効率よく忙しく働く」スタイルを選んでいます。
この世界では、
一つのタスクが長時間スタックを占拠すると、その間他の処理が一切動かない
だから「時間のかかるものは、できる限り非同期に逃がす」
という設計が重要になります。
ユーザー操作と画面更新も同じスレッドでやっている
クリック、キー入力、スクロール、アニメーション、レイアウト計算、再描画。
こういった UI に関わる処理も、基本は同じメインスレッドで行われます。
もしここに重い同期処理をぶち込むと、
クリックイベントの処理が遅れる
アニメーションがカクつく
スクロールが急に止まったように感じる
など、「ちょっとした不快さ」がどんどん積み上がっていきます。
非同期処理は、こうした「目に見えないラグ」や「気持ち悪さ」を減らして、
軽快な UI を保つための必須テクニック です。
非同期がないと何が辛いのか、もう一段踏み込む
1. ユーザー体験が壊れる
ユーザー目線で見たとき、同期オンリーの世界ではこうなります。
ボタンを押したら、画面が 3 秒固まる
入力中に、裏で何かの処理が走るとキー入力が詰まる
スクロールが途中で止まる
人間は 100〜200 ミリ秒を越えると「遅い」と感じ始めると言われます。
1 秒を超えるとかなりストレス。
3 秒止まると「壊れた?」と思い始めます。
非同期を使うことで、
処理中でもローディング表示を出せる
途中でキャンセルさせられる
部分的に結果を先に見せられる
といった「待ち時間の演出」が可能になり、体感はまるで変わります。
2. サーバー側(Node.js)でもスループットが死ぬ
Node.js のようなサーバーサイド JavaScript でも、非同期は超重要です。
もしファイル読み込みや DB アクセスを同期的にやると、
一つのリクエストが I/O 待ちの間、他のリクエストを処理できない
結果として、同時アクセス数が増えた瞬間にレスポンスが極端に遅くなる
という事態になります。
非同期 I/O を使えば、
ファイルや DB の返事を待っている間も、他のリクエストを処理できる
1 プロセスで多くの同時接続に対応できる
というメリットが出てきます。
JavaScript の「イベント駆動」「非同期 I/O」スタイルは、
少ないスレッドで大量の I/O を捌くための選択 でもあるわけです。
「なぜ非同期が必要か」をコードレベルで実感するための視点
「待ち時間はどこにあるか」を常に意識する
コードを書くときに、次の視点を持ってみてください。
この処理は、どこかで外部とのやりとりをしているか
時間がどれくらい読める処理か(数ミリ秒で終わる計算か、ネットワークか)
この処理が同期で止まっている間、ユーザーに何が起こるか
例えば、
巨大な JSON をパースする
画像を圧縮する
サーバーに問い合わせる
などは、「時間がかかるかもしれない処理」です。
ここを素の while や for でガッと回してしまうと、その瞬間 UI が固まります。
「待たせるなら、どう見せるか」まで含めて考える
非同期を使えるようになると、「待ち時間の設計」 ができます。
ローディングスピナーを出す
「保存中…」と表示する
先に一部だけ表示して、残りが来たら追記する
途中でキャンセルボタンを用意する
これらは全部、「非同期だからできること」です。
同期しかなければ、「終わるまで何も描画できない」が基本です。
ここが重要です。
非同期は、「コードの書き方が変わる」以上に、
「ユーザー体験の設計の幅を広げる技術」 だと思ってください。
まとめ:非同期が必要な理由を一文で言うと
非同期が必要な理由をぎゅっとまとめると、
「JavaScript はシングルスレッドなので、時間のかかる処理を同期で待つと UI や他の処理がすべて止まってしまう。
それを避けて、待っている間もアプリを動かし続けるために、非同期という仕組みが不可欠」
ということです。
そのうえで、
ユーザーが「固まった」と感じないようにするため
サーバー側で、多数の接続を効率よく捌くため
「待ち時間の演出」や「途中キャンセル」といった UX の工夫を可能にするため
非同期は、避けては通れない存在になっています。
これから Promise や async/await を学んでいくとき、
「なんでこんなややこしい書き方をするんだろう」と感じたら、
一度ここに立ち戻ってみてください。
「同期だけでやったら、どこが止まる? ユーザーにはどう見える?」
それをイメージし直すと、
非同期は「仕方なく覚える呪文」ではなく、
「ユーザーを待たせすぎないための、ちゃんと意味のある道具」 として見え方が変わってきます。
