まずタスクキューを一言でイメージする
タスクキューは、
「あとで実行する処理(コールバック)を順番に並べておく待ち行列」 です。
JavaScript はシングルスレッドなので、「今」実行できるのは 1 つだけ。
でも setTimeout や fetch、イベントリスナーなどで「あとで実行して」と予約された処理がたくさんあります。
それらを、
「終わった順」「発生した順」に並べて保管しておく場所が タスクキュー です。
そして、イベントループが「コールスタックが空いたタイミング」でタスクキューから 1 件ずつ取り出し、実行していきます。
ここが重要です。
タスクキューは「非同期処理のコールバックが“順番待ち”している場所」。
これを理解すると「なぜこの順番で実行されるの?」という疑問の多くがスッキリします。
タスクキューが使われる流れ(全体像)
登場人物のおさらい
非同期処理の世界には、ざっくりこういう役者がいます。
JavaScript エンジン(実際にコードを実行する)
コールスタック(今実行中の関数の積み重ね)
Web API(タイマー・通信・イベントなど「待つ仕事」を担当)
タスクキュー(「終わったら実行してね」という処理の待ち行列)
イベントループ(「スタックが空いたかな?」と見て、キューから次の処理を持ってくる係)
このうち、タスクキューは 「Web API が送り込んだコールバックを貯めておく場所」 です。
典型的な流れを一度言葉だけで追ってみる
- JavaScript が
setTimeoutやfetchを呼び出す - Web API がその「待つ仕事」を引き受ける(時間を測る・ネットワークする・イベントを待つ)
- 「準備できたよ」「完了したよ」というタイミングで、Web API が「このコールバックをタスクキューに入れておいて」と依頼する
- イベントループが、コールスタックが空いたタイミングでタスクキューを見に行き、先頭のものを取り出して実行する
つまり、
「非同期処理が終わった瞬間に即実行」ではなく、
一度タスクキューを経由して「スタックが空いたタイミングで順番に実行」される
というのがポイントです。
具体例1:setTimeout とタスクキュー
コードと出力順
console.log("A");
setTimeout(() => {
console.log("B");
}, 0);
console.log("C");
JavaScript実行すると、必ず
A
C
B
の順に出力されます。
「0ミリ秒なのに、なぜ B が最後なの?」という疑問が、タスクキューを理解すると腑に落ちます。
裏側で何が起きているか
- コールスタックに「グローバルコード」が積まれ、
console.log("A")が実行される setTimeoutが呼ばれ、「0ミリ秒後にこのコールバックを実行して」と Web API に依頼する
ここでsetTimeoutの処理は終わり、コールスタックから消えるconsole.log("C")が実行される- グローバルコードの実行が終わり、コールスタックが空になる
- 一方、Web API 側では「0ミリ秒」のタイマーが終わっており、「コールバックをタスクキューに投入」している
- イベントループが、コールスタックが空になったのを見て、タスクキューの先頭(このコールバック)を取り出し、コールスタックに積んで実行
console.log("B")が実行される
ここが重要です。setTimeout(..., 0) は「今すぐ実行」ではなく、
「今の処理(現在のコールスタック)が全部終わってから、タスクキュー経由で一番最初に実行される」 という意味だと理解すると、挙動がとても自然に見えてきます。
具体例2:イベントリスナーとタスクキュー
ボタンクリックのコード
const button = document.querySelector("#btn");
button.addEventListener("click", () => {
console.log("クリックされた");
});
console.log("イベントリスナーが登録されました");
JavaScriptこのコードでは、
ユーザーがボタンをクリックすると "クリックされた" が出力されます。
実際の流れ
- JavaScript が
addEventListenerを呼び、「クリックが起きたらこの関数を実行してね」と Web API に登録 - 一度登録してしまえば、JavaScript 側は何もしていない(ずっと待っているわけではない)
- ユーザーが実際にクリックすると、ブラウザのネイティブコードがそれを検知
- Web API が「クリックが起きたよ、このコールバックをタスクキューに入れて」とイベントループに渡す
- コールスタックが空いたタイミングで、イベントループがそのコールバックを実行
"クリックされた"が出力される
重要なのは、クリックイベントのコールバックも「タスクキューに入って、順番待ちしてから実行される」 ということです。
たとえユーザーが爆速で連打しても、
メインスレッドが空いたタイミングで、キューから順にイベントが処理されていきます。
具体例3:複数のタスクがキューに並ぶとどうなるか
ちょっと複雑な例
console.log("1");
setTimeout(() => {
console.log("2: timeout 0ms");
}, 0);
setTimeout(() => {
console.log("3: timeout 10ms");
}, 10);
console.log("4");
JavaScriptこのコードの出力順は、多くの環境で
1
4
2: timeout 0ms
3: timeout 10ms
のようになります(正確な順序はブラウザのタイマー実装に依存する部分もありますが、イメージとして)。
タスクキューの動きをイメージする
"1"がすぐに出力される- 0ms の
setTimeoutが Web API に登録される - 10ms の
setTimeoutも登録される "4"が出力される- グローバルコードが終わり、コールスタックが空になる
- その時点で、0ms のタイマーはすでに終了しており、「そのコールバック」がタスクキューの先頭に入っている
- イベントループがそれを取り出して
"2"を実行 - 少し後で 10ms が経過し、「3 のコールバック」がタスクキューに追加される
- 次にコールスタックが空いたときに
"3"が実行される
タスクキューは「先に入ったものから順に処理される FIFO の待ち行列」なので、
同じ種類のタスクなら、基本的には登録された順番で実行される と考えてよいです。
タスクキューとマイクロタスクキュー(軽く触れる)
実は、ブラウザの世界にはタスクキューが一種類だけではなく、
- 通常のタスクキュー(macrotask queue と呼ばれたりもする)
- マイクロタスクキュー(microtask queue)
という二つのレベルがあります。
ここではイメージだけ掴んでおけば十分です。
代表的な例
通常のタスクキューに入る代表setTimeout のコールバックsetInterval
DOM イベントのコールバック
マイクロタスクキューに入る代表Promise.then のコールバックasync/await の「await の先の処理」queueMicrotask で登録された処理
イベントループは、
「1つの通常タスクを処理し終わるたびに、次の通常タスクに行く前にマイクロタスクキューを全部空にする」
という順番で動きます。
その結果、Promise の then や await の続きが「かなり優先的に処理される」ことになります。
簡単な挙動確認の例
console.log("A");
setTimeout(() => {
console.log("B: timeout");
}, 0);
Promise.resolve().then(() => {
console.log("C: promise");
});
console.log("D");
JavaScript出力順はほとんどの環境で
A
D
C: promise
B: timeout
になります。
理由は、
- A → D は同期処理なのでそのまま
setTimeoutのコールバックは「通常のタスクキュー」Promise.thenのコールバックは「マイクロタスクキュー」- グローバルコードが終わってスタックが空になったタイミングで
まずマイクロタスク(C)を全部処理してから、
次に通常のタスク(B)に移る
というルールのためです。
ここが重要です。
「タスクキュー」という言葉を聞いたら、
「非同期コールバックが待っている場所」+「Promise など優先度が少し違う別のキューもある」
くらいのイメージを持っておくと、今後の async/await 学習がスムーズになります。
なぜタスクキューを知る必要があるのか
実行順の「なぜ?」に説明がつくようになる
タスクキューを知らないと、
「0ms の setTimeout がなぜ後回しになるのか」
「then の中の処理が、なぜこんな順番で実行されるのか」
が、ただの「覚えるしかないルール」に見えてしまいます。
タスクキューとイベントループの存在を知っていると、
「今はまだグローバルの処理がスタックに乗っているから、タスクキューの中身は後回しだな」
「ここで Promise を解決したから、マイクロタスクキューに then が積まれたな」
というふうに、「構造」で理解できるようになります。
ブロッキングとの関係も見えてくる
タスクキューにいくらコールバックが溜まっていても、
コールスタックが「重い同期処理」に占拠されていたら、
イベントループはタスクキューから何も取り出せません。
その結果、
- クリックイベントが反応しない
setTimeoutの実行が遅れる- Promise の then がなかなか走らない
という症状になります。
つまり、
「タスクキューに入っているから安全」ではなく、「スタックを長時間塞がない書き方」がセットで重要 だと分かります。
まとめ
タスクキューの本質は、
「非同期処理のコールバックが、実行されるまで順番に並んで待っている場所」
です。
押さえておきたいポイントを整理すると、
非同期処理(タイマー・通信・イベントなど)が完了したとき、Web API はコールバックを直接実行せず、まずタスクキューに入れる
イベントループは、コールスタックが空になったタイミングでタスクキューの先頭を取り出して実行するsetTimeout(..., 0) は「今すぐ」ではなく、「現在の処理が全部終わったあと、タスクキューの先頭で処理される」という意味
Promise.then や await の続きは「マイクロタスクキュー」に入り、通常タスクより優先して処理される
重い同期処理でコールスタックを塞ぐと、タスクキューにいくら溜まっても実行されない(=UI が固まる)
まずは、
setTimeoutPromise.resolve().then(...)- 簡単なイベントリスナー
を組み合わせた小さなコードを書いて、
「どのタイミングでどのコールバックがタスクキューに積まれ、どの順番で実行されているか」を意識しながら実行結果を見てみてください。
タスクキューとイベントループのイメージが掴めると、
JavaScript の非同期処理は「暗記する謎のルール」から、
「構造として納得できる仕組み」に変わります。
