🔄

JavaScriptのイベントループについてまとめる

に公開

こんにちは!
スペースマーケットでフロントエンドエンジニアをしているk___0122です。
皆さん最近海外旅行に行かれましたか?僕は今年の6月にインドに行ってきました🇮🇳
ガンジス川に入ったのがいい思い出です🛁

ということで突然ですが、以下のコードを実行するとどの順番で出力されると思いますか??

function hoge() {
  console.log('hoge');
}

function timer() {
  setTimeout(function fuga() {
    console.log('fuga');
  }, 0);
}

function piyo() {
  console.log('piyo');
}

hoge();
timer();
piyo();
答え
hoge
piyo
fuga

どうでしょう皆さん合ってましたか😇
ではなぜこのような挙動をするのでしょうか。
非同期処理が行われた時の挙動を見ていきましょう。

そもそもJavaScriptはシングルスレッドです。非同期的に処理を行わず、上から順に同期的に処理を行います。
ただ同期的にしか処理を行えないと、時間のかかる重たい処理がある場合、その処理が終わるまで何もできなくなります。
試しにwhile文を使って重たい処理を実行してみます。

function task(message) {
    let n = 10000000000;
    while (n > 0){
        n--;
    }
    console.log(message);
}

console.log('Start');
task('Call an API');
console.log('Done');

実行するとtask関数の実行が完了するまでDoneは表示されません。
task関数の中身がwhile文ではなく、APIを叩いてデータを取得する処理だった場合、データの取得が完了するまでユーザーは何も操作ができなくなってしまいますよね。
そこでJavaScriptではイベントループというものを使って、非同期処理を行なっています。

イベントループ

以下が処理の全体像です。

コールスタック

まず関数が呼ばれるとコールスタックに積まれまていきます。
以下のコードをJavaScript Visualizer 9000 を使って挙動確認してみます。
(Gif用意してますが、見づらいかもしれないので、実際にVisulaizerを使って確認してみてください!)

function piyo() {
  console.log('piyo');
}

function fuga() {
  piyo()
  console.log('fuga');
}

function hoge() {
  fuga()
  console.log('hoge');
}

hoge();

コールスタックでは関数が呼ばれた順番に積まれていき、一番上に積まれた関数を実行します。
関数の実行が完了するとコールスタックからポップします。(LIFO形式
今回例では、hoge→fuga→piyoの順で積まれ、処理が完了するとpiyo→fuga→hogeの順番でポップしていきます。

Web APIs

ブラウザが提供する機能です。
DOM,Ajax,setTimeout,Promise,Fetchなどがあります。
コールスタックで関数を実行した際、Web APIから提供されてるものがあれば、Web API上で実行されます。
例えばWeb APIから提供されるsetTimeoutを実行すると、Web API上でsetTimeoutを行い、待機が完了すると、setTimeoutのコールバック関数をコールバックキューに渡します。
https://developer.mozilla.org/ja/docs/Web/API

コールバックキュー

Web APIから受け取ったコールバック関数が待機する場所がコールバックキューになります。
またブラウザによってマイクロタスクキューとマクロタスクキュー(タスクキュー)2種類のキューがあります。

  • マイクロタスクキュー
    優先度:高
    Promiseなどの非同期処理
  • マクロタスクキュー(タスクキュー)
    優先度:低
    setTimeout,setIntervalなど

マイクロタスクキュー > マクロタスクキュー(タスクキュー)の順で処理されます。
試しに以下のコードを実行してみるとpromise→setTimeoutの順で処理されます。

setTimeout(function timer() {
  console.log('setTimeout');
}, 0);

Promise.resolve('promise').then(function promise(r) {
  console.log(r);
});

sample

動作を確認してみる

一通りの解説が終わったところで、一番最初に質問したコードを確認してみます。

function hoge() {
  console.log('hoge');
}

function timer() {
  setTimeout(function fuga() {
    console.log('fuga');
  }, 0);
}

function piyo() {
  console.log('piyo');
}

hoge();
timer();
piyo();

sample3

もう一度全体像も確認してみましょう。

hogeを実行すると、コールスタックに積まれ、処理が完了するとhogeはコールスタックからポップします。
次にtimer関数が実行されます。timer関数がコールスタックに積まれると、setTimeoutを実行します。
setTimeoutはWeb APIから提供されるものなので、Web API側で0ms待機します。
待機が完了するとsetTimeoutのコールバック関数fugaがタスクキューに渡ります。

その間にpiyoがコールスタックが積まれ、処理が完了するとコールスタックからポップします。
コールスタックが空になったタイミングで、イベントループがコールスタックにfugaを追加し実行します。

このようにイベントループという機構を使ってJavaScriptは非同期処理を実現してることが分かりました!

最後に

では最後に宣伝です!
スペースマーケットでは、一緒にサービスを成長させていく仲間を探しています。
とりあえずどんなことをしているのか聞いてみたいという方も大歓迎です!
ご興味ありましたら是非ご覧ください!
https://www.wantedly.com/projects/1113570
https://www.wantedly.com/projects/1113544
https://www.wantedly.com/projects/1061116
https://spacemarket.co.jp/recruit/engineer/

参考

https://dev.to/lydiahallie/javascript-visualized-event-loop-3dif
https://www.javascripttutorial.net/javascript-event-loop/
https://www.youtube.com/watch?v=8aGhZQkoFbQ

スペースマーケット Engineer Blog

Discussion