2
2

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

JavaScriptのイベントループ完全理解:非同期処理を制御するタスクキューと実行モデルの本質

Posted at

概要

JavaScriptの非同期処理は、イベントループ(Event Loop)という実行モデルの上で動いている。
この仕組みを正しく理解することで、以下のような非同期設計が可能になる:

  • なぜ Promise.then()setTimeout より先に呼ばれるのか?
  • UIが固まる処理を、どうすれば回避できるのか?
  • ループ内の非同期処理は、なぜ意図通りに動かないのか?

この記事では、イベントループの構造・マイクロタスクとマクロタスクの違い・UIと非同期の干渉などを整理し、非同期処理の裏側を設計視点で読み解く。


イベントループの基本構造

[Call Stack] ← 同期処理

[Task Queue] ← マクロタスク:setTimeout, setInterval, I/O

[Microtask Queue] ← マイクロタスク:Promise, queueMicrotask

Event Loop:
  1. Call Stack が空になるのを待つ
  2. Microtask Queue を全て実行
  3. 次に Task Queue の先頭を 1 件実行
  4. 繰り返す

タスクの種類

タイプ 実行タイミング 代表API
マクロタスク 各イベントループの最後 setTimeout, setInterval, setImmediate, MessageChannel, I/O
マイクロタスク 各イベントループの中間 Promise.then, catch, finally, queueMicrotask

実行順の違い:コードで検証

setTimeout(() => console.log('timeout'), 0);
Promise.resolve().then(() => console.log('promise'));
console.log('sync');

出力順:

sync
promise
timeout

✅ 理由

  • console.log('sync'): Call Stack 上の同期処理
  • .then(...): Microtask Queue に追加
  • setTimeout: Task Queue に追加
  • Event Loop は、同期 → Microtask → Task の順で実行

Microtask の優先度

Promise.resolve().then(() => {
  console.log('first');
  Promise.resolve().then(() => console.log('second'));
});

出力順:

first
second

→ ✅ Microtask はEvent Loop内で完全に消化されるまで続く


queueMicrotask の使いどころ

queueMicrotask(() => {
  console.log('executed last (but before setTimeout)');
});

→ ✅ UIをブロックせず、“でも極力早く”実行したいときに使える


UIとイベントループ:ブロッキング設計を避ける

❌ 悪い例(同期処理でUIを固める)

document.getElementById('btn').addEventListener('click', () => {
  // 長い処理
  for (let i = 0; i < 1e9; i++) {}
  alert('done');
});

→ ✅ イベント処理中は Call Stack が詰まるため、UI更新も遅延する


✅ 解決策:非同期化して分割実行

document.getElementById('btn').addEventListener('click', () => {
  setTimeout(() => {
    for (let i = 0; i < 1e9; i++) {}
    alert('done');
  }, 0);
});

設計におけるベストプラクティス

目的 推奨戦略
優先度の高い非同期処理 Promise.then / queueMicrotask
長い処理でUIを固めないようにしたい setTimeout(..., 0) で分割
イベント後に直ちに実行(でも非同期) queueMicrotask()
複数非同期ステップを順序制御したい async/await + Promise

よくある誤解

setTimeout(..., 0) は“すぐに実行”される?

→ ❌ 違います。“最速でも1ループ後”
→ ✅ Microtask (Promise) の方が先に処理される


❌ Promise の中に setTimeout を入れても Microtask にはならない

new Promise(resolve => {
  setTimeout(() => {
    resolve('done');
  }, 0);
});

setTimeout はマクロタスク。Promise内でも Microtask にはならない


結語

JavaScriptの非同期処理は、構文ではなく“タイミングの設計”である。
その制御の基盤となるのが、イベントループとタスクキューである。

  • 同期 → Microtask → Macrotask の流れを理解することで、
  • 非同期の「なぜ今動かない?」を制御できるようになり、
  • UIとロジックの衝突を避けた洗練された設計が可能になる

構文を知っていても、タイミングを知らなければ設計はできない。
イベントループとは、「時間の構文」である。

2
2
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
2
2

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?