JavaScript | 非同期処理:非同期の基礎概念 – ブロッキングとは何か

JavaScript JavaScript
スポンサーリンク

「ブロッキング」とは何か(まずイメージから)

「ブロッキング」は、
ある処理が終わるまで、そこから先の処理が一切進めなくなる状態
のことです。

JavaScript だと「一番の働き手(メインスレッド)が、その処理に捕まってしまい、他のことが何もできない状態」と考えるといいです。

ブラウザなら、画面が固まる、クリックしても反応しない、スクロールもできない。
Node.js なら、他のリクエストの処理が止まってしまう。
こういう症状が出ているとき、「ブロッキングしている」と言えます。

ここが重要です。
ブロッキングは「処理が遅い」のではなく、
「待っている間も、他の仕事を全く並行できていない」ことこそが問題 です。


ブロッキングの具体例(ブラウザ編)

重い処理を同期でやったときに何が起こるか

まずは分かりやすく、ブラウザで「3秒間重い処理をする」コードを書いてみます。

console.log("A: 開始");

const start = Date.now();
while (Date.now() - start < 3000) {
  // 3秒経つまでひたすら回り続ける
}

console.log("B: 終了");
JavaScript

このコードをブラウザのコンソールで実行すると、

  1. “A: 開始” が出る
  2. 3 秒間、ブラウザがほぼ固まる
  3. 3 秒後に “B: 終了” が出る

この「3秒間」のあいだ、画面のスクロールやクリックにほとんど反応しなくなります。
JavaScript のメインスレッドが、この while ループに占拠されているからです。

これが典型的な「ブロッキング」です。
メインスレッドが重い処理に捕まって、
ユーザーイベントや描画の更新を処理できなくなっています。

setTimeout は「ブロッキングしない」待ち方

今度は、同じ 3 秒の待ち時間を setTimeout で書いてみます。

console.log("A: 開始");

setTimeout(() => {
  console.log("B: 3秒経過");
}, 3000);

console.log("C: 他の処理");
JavaScript

実行順は、

  1. “A: 開始”
  2. すぐに setTimeout の予約がされる(ここでほぼ時間はかからない)
  3. “C: 他の処理” がすぐ出る
  4. 3 秒経ったら “B: 3秒経過” が出る

ポイントは、「3 秒待っている間も、スクロールやクリックにきちんと反応できる」ことです。

なぜかというと、

非同期の待ち時間は、JavaScript メインスレッドそのものが待っているわけではなく、
「3 秒たったらこの関数を実行してね」という予約だけして、
メインスレッド自身は次の仕事に進んでいるからです。

ここが重要です。
同じ「3秒待つ」でも、
同期で「3秒間ひたすらループ」するのはブロッキング、
非同期で「3秒後に実行してと予約」するのはノンブロッキング。
見た目が似ていても、アプリへの影響は天と地ほど違います。


ブロッキングがなぜ JavaScript で特に問題になるのか

JavaScript は基本「1本のメインスレッド」だから

ブラウザの JavaScript は、基本的に 1 本のメインスレッドがすべての仕事を担当しています。

この 1 本が、次のようなことを全部やっています。

画面の描画更新
クリックやキー入力などのイベント処理
あなたが書いた JavaScript コードの実行

ここにブロッキング処理を入れてしまうと、その間、この 1 本が完全に塞がってしまいます。
結果として、ユーザーからは「固まった」「落ちた?」と見えてしまうわけです。

Node.js でも同じ構造

Node.js も「イベントループ+非同期 I/O」を軸にした、
1 本のメインスレッドで動くスタイルが基本です。

もし Node.js で重い同期処理を書いてしまうと、そのプロセスで処理している全リクエストに影響します。

例えば、同期的なファイル読み込み(fs.readFileSync)を大量のリクエストの中で使うと、
その読み込みが終わるまで他のリクエストを処理できず、
一気にレスポンスが遅くなります。

ここが重要です。
JavaScript(ブラウザも Node も)は、「重い同期処理に弱い」構造をしている。
だからこそ、ブロッキングを避けて非同期に逃がす設計がとても重要になる、ということです。


ブロッキングと非ブロッキングの違いをコードで比べる

悪い例:重い同期処理のせいでボタンが反応しない

ブラウザで、次のようなコードを想像してください。

const button = document.querySelector("#heavyButton");

button.addEventListener("click", () => {
  console.log("クリックされた! 重い処理を開始");

  const start = Date.now();
  while (Date.now() - start < 5000) {
    // 5秒間フリーズ
  }

  console.log("重い処理が終了");
});
JavaScript

このボタンをクリックすると、

「クリックされた!」が表示されたあと、5秒間ブラウザが固まり、
その後「重い処理が終了」と出ます。

5 秒間、他のボタンを押してもイベントは処理されず、
スクロールもカクカク、もしくは止まったように見えます。

これが「イベントハンドラ内のブロッキング」です。
UI イベントを処理する関数の中に重い同期処理を入れると、
体感として「アプリが壊れた」ように見えてしまいます。

より良い例:重い処理を分割して、隙間でイベントを処理させる

本当に重い計算を完全に避けられない場合でも、
短い区切りに分けて、途中でイベントループに制御を返す書き方ができます。

簡略化した例です。

function heavyTaskChunked(total, chunkSize = 1000000) {
  let processed = 0;

  function processChunk() {
    const start = Date.now();

    while (processed < total) {
      // 何か重い計算
      processed++;

      if (processed % chunkSize === 0) {
        break; // 一旦区切る
      }
    }

    if (processed < total) {
      setTimeout(processChunk, 0); // 0ms で次のチャンクを予約
    } else {
      console.log("全部終わった");
    }
  }

  processChunk();
}
JavaScript

このように「少し処理する → すぐ終わって制御を返す → また少し処理する」を繰り返すと、
その合間にクリックや描画更新などが挟まれて、体感のフリーズを軽減できます。

ここが重要です。
完全にブロッキングさせる「長時間の同期処理」を避け、
「短く区切って合間にイベントループへ戻す」「非同期タスクとして外に逃がす」
という工夫が、JavaScript では特に大事になります。


「ブロッキングしない」ために意識すべきこと

時間がかかる処理は基本すべて疑う

JavaScript では、次のような処理は「ブロッキングの候補」です。

長い whilefor ループ
巨大な配列を一気に処理する
同期的なネットワーク I/O(Node.js の一部関数など)
同期的なファイル I/O(readFileSync など)

これらを、そのまま 1 回のイベントハンドラの中で実行すると、
その時間だけメインスレッドが完全に塞がります。

ブラウザなら「クリックしてから反応するまで 1〜2 秒かかる UI」
Node.js なら「あるリクエストに重い処理が入ると、他のリクエストが遅延する」
という現象になって現れます。

非同期 API を優先して使う

Node.js なら、同じ目的の処理でも

同期 API(readFileSync, writeFileSync など)はブロッキング
非同期 API(readFile, writeFile など)はノンブロッキング

というペアになっていることが多いです。

可能な限り、非同期版の API を選び、
結果はコールバック・Promise・async/await で受け取るようにします。

ブラウザでも、
fetch は非同期でノンブロッキングなので積極的に使い、
「重い同期ループ」を避ける、処理を分割する、Web Worker に逃がす
といった設計を考えると良いです。


まとめ:ブロッキングを一言で言うと

ブロッキングの一番大事な要約はこれです。

「その処理のあいだ、JavaScript のメインスレッドが他の仕事を何もできない状態にしてしまうこと」

そしてその結果として、

ブラウザでは
画面が固まる
クリックに反応しない
スクロールできない

Node.js では
他のリクエストの処理が遅れる
全体のスループットが落ちる

といった問題が起こります。

だからこそ、

時間がかかる処理はできるだけ非同期 API に逃がす
どうしても同期計算が必要なら、短いチャンクに分割して合間にイベントループへ制御を返す

という意識が、JavaScript の「非同期処理設計」の土台になります。

この「ブロッキングの感覚」が腹に落ちていると、
これから学ぶ Promise や async/await、イベントループの仕組みも、
「すべてはメインスレッドを塞がないための工夫なんだ」と、一本の線でつながって見えてきます。

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