JavaScript | 非同期処理:非同期の基礎概念 – 非同期が必要な理由

JavaScript JavaScript
スポンサーリンク

まず「なぜ非同期なんて面倒なものがあるのか」

いきなり本音から言うと、多くの人はこう感じます。

「同期処理だけで書けたらどれだけ楽か」
「非同期って急に難しくなるし、できれば避けたい」

実は 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 を学んでいくとき、
「なんでこんなややこしい書き方をするんだろう」と感じたら、
一度ここに立ち戻ってみてください。

「同期だけでやったら、どこが止まる? ユーザーにはどう見える?」
それをイメージし直すと、
非同期は「仕方なく覚える呪文」ではなく、
「ユーザーを待たせすぎないための、ちゃんと意味のある道具」 として見え方が変わってきます。

タイトルとURLをコピーしました