Webエンジニア向けプログラミング解説動画をYouTubeで配信中!
▶ チャンネル登録はこちら

【ITニュース解説】JavaScript's Event Loop

2025年09月21日に「Dev.to」が公開したITニュース「JavaScript's Event Loop」について初心者にもわかりやすく解説しています。

作成日: 更新日:

ITニュース概要

JavaScriptのイベントループは、シングルスレッドでも非同期処理をスムーズに実行する仕組みだ。同期処理は即座に実行され、PromiseなどのマイクロタスクはsetTimeoutなどのマクロタスクより優先される。この順序でJSは動作している。

出典: JavaScript's Event Loop | Dev.to公開日:

ITニュース解説

JavaScriptはWebブラウザやNode.jsといった環境で動作するプログラミング言語であり、その最も重要な特徴の一つは「シングルスレッド」であることだ。シングルスレッドとは、一度に一つの処理しか実行できないことを意味する。しかし、ウェブアプリケーションでは、ネットワークからデータを取得したり、タイマーで時間を計ったり、ユーザーのクリックに反応したりと、時間がかかる様々な処理を同時に、あるいは並行して実行する必要がある。もしこれらの処理が一つずつ完全に終わるまで待機してしまうと、アプリケーション全体が一時停止し、ユーザーは何も操作できなくなる。このような問題を解決し、シングルスレッドでありながらもスムーズに非同期処理を実現するための核心的なメカニズムが「イベントループ」である。

イベントループとは、JavaScriptの実行環境において、プログラムが継続的に動作し、非同期タスクを効率的に処理するための監督者の役割を果たす。この仕組みを理解するためには、いくつかの重要な構成要素とその連携について知る必要がある。

まず、「コールスタック」は、JavaScriptコードが実際に実行される場所だ。関数が呼び出されるたびに、その関数がスタック(積み重ねられたデータ構造)の一番上に積まれ、実行が完了するとスタックから取り除かれる。同期的な処理、つまり上から順に実行される処理はすべて、このコールスタック上で処理される。

次に、「Web API」について説明する。これはブラウザやNode.jsなどのJavaScript実行環境が提供する、非同期処理を行うための機能群のことだ。例えば、setTimeoutは指定した時間後にコードを実行するための機能であり、fetchはネットワークからデータを取得するための機能である。JavaScriptのコードでこれらのWeb APIが呼び出されると、その処理はコールスタックからWeb APIに渡され、JavaScriptのメインスレッドをブロックすることなく、バックグラウンドで非同期に処理が開始される。

Web APIでの処理が完了すると、その結果や、後に実行されるべきコールバック関数が、二種類の異なる待機場所へと送られる。これらが「タスクキュー」(マクロタスクキューとも呼ばれる)と「マイクロタスクキュー」である。

「タスクキュー」には、setTimeoutsetIntervalのコールバック関数、DOMイベント(ユーザーのクリックやキー入力など)のコールバック関数、ネットワークからの応答(例えばfetch APIの結果)のコールバック関数などが積まれる。これらのタスクは、Web APIでの処理が完了し次第、このキューの末尾に追加される。

一方、「マイクロタスクキュー」は、タスクキューよりも高い優先度を持つキューだ。主にPromiseのthen()catch()finally()メソッドに登録されたコールバック関数が、Promiseが解決または拒否された結果としてこのマイクロタスクキューに積まれる。

これらの要素がどのように連携してイベントループを形成し、コードを実行するのか。イベントループは、基本的に「コールスタックが空になったとき」に動作を開始する。つまり、JavaScriptの同期的な処理がすべて完了し、コールスタックに何も残っていない状態になったときに、イベントループは次に実行すべき処理を探し始めるのだ。

イベントループが次に実行すべき処理を探す際、まず「マイクロタスクキュー」を優先的に確認する。もしマイクロタスクキューにタスクがあれば、イベントループはそこにあるタスクを「すべて」取り出し、一つずつコールスタックに送り込んで実行する。このとき、もしマイクロタスクの実行中に新たなマイクロタスクが生成されても、それは同じマイクロタスクキューに追加され、イベントループはマイクロタスクキューが完全に空になるまで処理を続ける。

マイクロタスクキューが空になると、イベントループは次に「タスクキュー」(マクロタスクキュー)を確認する。もしタスクキューにタスクがあれば、イベントループはそこから「一つだけ」タスクを取り出し、コールスタックに送り込んで実行する。このタスクの実行が終わり、コールスタックが再び空になると、イベントループはまたマイクロタスクキューの確認からこのサイクルを繰り返す。このように、イベントループは「コールスタックが空になるたびに、マイクロタスクキューをすべて処理し、その後タスクキューから一つだけタスクを取り出して処理する」というサイクルをひたすら繰り返すことで、シングルスレッドのJavaScriptが非同期処理を効率的に実行できるようにしている。

それでは、具体的なコード例を通して、このイベントループの動作原理を深く理解してみよう。

1console.log("Start");
2
3setTimeout(() => {
4  console.log("Timeout");
5}, 0);
6
7Promise.resolve().then(() => {
8  console.log("Promise");
9});
10
11console.log("End");

このコードが実行されると、まず最初のconsole.log("Start")がコールスタックに積まれ、すぐに実行される。そのため、出力は「Start」となる。

次に、setTimeout(() => { console.log("Timeout"); }, 0)が実行される。これはWeb APIの一つなので、setTimeoutの呼び出しはコールスタックからWeb APIに渡される。Web APIは指定された0ミリ秒(実際には最短時間)のタイマーを開始し、コールバック関数() => { console.log("Timeout"); }は、タイマーが切れた後にタスクキューへと移動するための準備を始める。この時点ではまだ何も出力されない。

続いて、Promise.resolve().then(() => { console.log("Promise"); })が実行される。Promise.resolve()は即座に解決済みのPromiseオブジェクトを返す。そのthen()メソッドに登録されたコールバック関数() => { console.log("Promise"); }は、Promiseが解決された結果として、マイクロタスクキューへと送られる。この時点でもまだ何も出力されない。

最後に、console.log("End")がコールスタックに積まれ、すぐに実行される。出力は「End」となる。

この時点で、すべての同期処理が完了し、コールスタックは空になる。すると、イベントループが動作を開始する。イベントループはまずマイクロタスクキューを確認する。マイクロタスクキューには、先ほど追加されたPromiseのコールバック関数() => { console.log("Promise"); }が存在する。イベントループはこのタスクをコールスタックに送り込み、実行する。その結果、出力は「Promise」となる。

マイクロタスクキューが空になったことを確認したイベントループは、次にタスクキューを確認する。タスクキューには、setTimeoutのコールバック関数() => { console.log("Timeout"); }が存在する(タイマーは0ミリ秒に設定されていたため、Web APIでの待機はすでに終わり、タスクキューに移動済みである)。イベントループはこのタスクをタスクキューから一つ取り出し、コールスタックに送り込み、実行する。その結果、出力は「Timeout」となる。

これでコールスタックもタスクキューもマイクロタスクキューもすべて空になったため、イベントループは待機状態に入るが、実行すべきタスクはもう残っていない。

このようにして、最終的な出力順序は「Start → End → Promise → Timeout」となる。

この一連のプロセスは、JavaScriptがシングルスレッドでありながらも、ユーザーインターフェースをブロックせずに非同期処理を実行できる秘訣である。イベントループは、コールスタック、Web API、タスクキュー、マイクロタスクキューといった要素が連携し、効率的にタスクをスケジューリングすることで、アプリケーションの応答性を維持している。システムエンジニアとしてJavaScriptアプリケーションを開発する上で、このイベントループの仕組みを理解することは、パフォーマンスの高い、ユーザーフレンドリーなコードを書くための基礎となる。非同期処理の振る舞いを予測し、適切な設計を行うためには、イベントループの深い知識が不可欠である。

関連コンテンツ