setTimeout の真の力、あなたは知っていますか？

こんにちは。[ぬこすけ](https://twitter.com/nuko_suke_dev) です。

皆さんは「 `setTimeout` とはどんな関数でしょう？」と聞いたら、どう答えますか？

おそらく、ほとんどの人が「指定した時間に処理が走るようにする関数」と答えるのではないでしょうか？

```javascript
function main() {
  console.log('動いたよ！！');
}

// 大体 ３ 秒後に main 関数が動き出す
setTimeout(main, 3000);
```

大雑把な理解としては問題ないですが、実は **`setTimeout` の隠された能力はそれだけではありません**。

`setTimeout` の隠された能力を知るとどんな良いことがあるのでしょうか？
例えば、次のような良いことがあります。

* **ブラウザに対する理解が深まる**
* **Web サイトのパフォーマンスを向上させることができる**
* **ドヤれる**

まず、 **ブラウザに対する理解が深まります** 。
かのアリストテレスはこう言い残しました。

>Knowing setTimeout is the beginning of all wisdom.
( setTimeout を知ることは、すべての知恵の始まりである)

**`setTimeout` を知ることは（ブラウザの）すべての知恵のはじまり** と言って過言ではありません。
普段なにげなく `setTimeout` を使っていても、「実はブラウザは裏でこんなことしているんだー」という新しい知見を得ることができます。

また、 `setTimeout` を使いこなせれば **Web サイトのパフォーマンス改善が期待** できます。
SEO で重要な Core Web Vitals のスコアを上げることにもつながるでしょう。

あと、 **ドヤれます** 。
職場のベテランエンジニアや、転職時のエンジニア面接官に「 `setTimeout` とはどんな関数でしょう？」と聞いてみてください。
単に「指定した時間に処理が走るようにする関数」と答えたら、 **あなたの方が知識は上** です。ドヤってください。

ということで、この記事では `setTimeout` の隠された能力について解説します。
本来、難しめな内容なのですが **初心者の方にもわかりやすく説明していきます** 。

## `setTimeout` の隠された秘密とは...?

`setTimeout` の真の能力、それは **タスクを分割してタスクキューに追加してくれる** ことです。

![boy_question.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/164159/851f9cf6-99bd-bb6b-8f2f-6fcf66b18c48.png)

この記事を閉じようとしたそこのあなた、 **ちょっと待ってください！！**
ちゃんと **初心者の方にもわかりやすく説明します!**

わかっています。「タスク」と「タスクキュー」ってなに？という話でしょう。

「タスク」については次の通りです。

>タスクとは、プログラムの初期実行、イベントコールバックの実行、インターバルやタイムアウトの発生など、標準的なメカニズムによって実行がスケジュールされる JavaScript コードのことです。これらはすべてタスクキューにスケジューリングされます。
https://developer.mozilla.org/ja/docs/Web/API/HTML_DOM_API/Microtask_guide

![boy_question.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/164159/851f9cf6-99bd-bb6b-8f2f-6fcf66b18c48.png)

**記事を閉じようとしないでください！！**
この説明だと私もタスクが何かわかりません🤯

**タスクについては具体例を挙げた方がイメージがつかみやすい** です。
例えば、次のような `script` タグのある HTML があったとします。

```html
<script>
  function main() {
    console.log("スクリプト");
  }
  main();
</script>
```

ブラウザはこの HTML を読み込むと、 `script` タグ内のコード `main` 関数を実行します。
この時 `main` 関数の実行が「タスク」です。

もう１個例を挙げてみましょう。
次のような JavaScript があったとします。

```javascript
function a() {
  console.log('a');
}

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

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

function main() {
  a();
  b();
  c();
}

window.addEventListener('load', main);
```

この例では、 `load` というイベントに `main` という関数をコールバックとして登録しており、ページが完全に読み込まれると `main` 関数が実行されます。
この `main` も「タスク」です。

関数 `a` や `b` 、 `c` はどうでしょうか？
**この関数たちはタスクではなく `main` というタスクの中で実行される作業** と捉えてください。
後でお話しますが、 **ここは重要なポイント** です。

さて、なんとなくタスクのイメージをつかめたでしょうか？
もしわかりにくかったら「グルーピングしたひとまとまりの処理」くらいのイメージで大丈夫です。

>`setTimeout` の真の能力、それは **タスクを分割してタスクキューに追加してくれる** ことです。

一方で「タスクキュー」とはなんでしょうか？
これは「 TODO リスト」だったり「やることリスト」のようなイメージを持ってもらえれば OK です！
「 TODO 」や「やること」が「タスク」に相当します。

### 家事に例えると

ここまでの話を家事に例えてみましょう。

![family_kaji_tetsudai.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/164159/f4bd9e39-5a6f-d001-09ba-17f90432c210.png)

料理や洗濯、掃除というタスクがあるとした時、タスクキュー（ TODO リスト）は次のようなイメージです。

![スクリーンショット 2022-10-21 17.53.33.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/164159/e168ac33-2d02-d342-133c-623d3fac1c36.png)

家事をこなすのはブラウザ主婦(主夫)です（以降、ブラウザママとします）。
ブラウザママは左から順番に料理や洗濯、掃除をこなしていきます。

まず、料理をします。
「冷蔵庫から材料を取り出す」「材料を切る」などの作業をこなしていきます。
（先ほどの 関数 `a` や `b` 、 `c` の例がこれにあたります）

料理を終えたら次のようなタスクキューになります。
次に洗濯というタスクをこなそうとします。

![スクリーンショット 2022-10-21 17.57.36.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/164159/f380a09d-d95c-f88c-b33a-d55ba049eb35.png)

このようにブラウザはタスクキューにあるタスクを１つずつこなしていきます。
1つのタスクの中にも「冷蔵庫から材料を取り出す」「材料を切る」などの細かい作業があるので、頑張ってこなします。

このブラウザママですが、かなり律儀です。
**料理が完了するまで他のことはしません** 。

料理をソースコードで表現してみましょう。

```javascript
function calculateCalorie() {
  console.log('料理のカロリー計算をする');
}

function takePotato() {
  console.log('じゃがいもを冷蔵庫から持ってくる');
}

function slicePotato() {
  console.log('じゃがいもを切る');
}

function boilPoteto() {
  console.log('じゃがいもをゆでる');
}

function cook() {
  // 全部終わるまで次のタスクへいけない
  calculateCalorie();
  takePotato();
  slicePotato();
  boilPoteto();
}

window.addEventListener('昼ごはん', cook);
```

ブラウザママは律儀なので、今日作る料理のカロリー計算をしてから、じゃがいもを料理します。
これが完了後に次の洗濯をします。

さて、いつも律儀に家事をこなすブラウザママですが、めちゃくちゃ忙しい日もあります。
子供の送り迎えや買い物もしなくちゃいけません。

![karou_hoikushi_woman.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/164159/2c671775-a074-609b-c633-ec6029ebe1f1.png)

ブラウザママは考えました。
**「料理中のカロリー計算は後回しにしよう！」** と。

```javascript
function calculateCalorie() {
  console.log('料理のカロリー計算をする');
}

function takePotato() {
  console.log('じゃがいもを冷蔵庫から持ってくる');
}

function slicePotato() {
  console.log('じゃがいもを切る');
}

function boilPoteto() {
  console.log('じゃがいもをゆでる');
}

function cook() {
  setTimeout(calculateCalorie, 0); // setTimeout の出番！！！
  takePotato();
  slicePotato();
  boilPoteto();
}

window.addEventListener('昼ごはん', cook);
```

おや、`setTimeout` が出てきました。
この処理を入れることでどうなったのでしょうか？

ブラウザママのタスクキューはこうなりました。

![スクリーンショット 2022-10-21 18.47.44.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/164159/dfb6cfc0-fb44-9ff8-e9f5-7b06b531b929.png)

**カロリー計算が後回しになっています** 。
ブラウザママはカロリー計算をしない分、料理を早く終えることができ、さらに他の洗濯や掃除などのタスクを優先的にこなせるようになりました。

>`setTimeout` の真の能力、それは **タスクを分割してタスクキューに追加してくれる** ことです。

なんとなくおわかりいただけたでしょうか？
言い換えれば、 `setTimeout` の真の力は **タスク内の一部作業を後回しにしてくれる** というものなのです。

:::note info
興味があれば、今回のブラウザママの料理の様子は [JavaScript Visualizer 9000](https://www.jsv9000.app/?code=ZnVuY3Rpb24gY2FsY3VsYXRlQ2Fyb3JpZSgpIHsKICBjb25zb2xlLmxvZygnY2FsY3VsYXRlQ2Fyb3JpZScpOwp9CgpmdW5jdGlvbiB0YWtlUG90YXRvKCkgewogIGNvbnNvbGUubG9nKCd0YWtlUG90YXRvJyk7Cn0KCmZ1bmN0aW9uIHNsaWNlUG90YXRvKCkgewogIGNvbnNvbGUubG9nKCdzbGljZVBvdGF0bycpOwp9CgpmdW5jdGlvbiBib2lsUG90YXRvKCkgewogIGNvbnNvbGUubG9nKCdib2lsUG90ZXRvJyk7Cn0KCmZ1bmN0aW9uIGNvb2soKSB7CiAgc2V0VGltZW91dChjYWxjdWxhdGVDYXJvcmllLCAwKTsKICB0YWtlUG90YXRvKCk7CiAgc2xpY2VQb3RhdG8oKTsKICBib2lsUG90YXRvKCk7Cn0KCmNvb2soKTs%3D) というサイトから確認できます。
「 Run 」ボタンをクリックした後、 「 Step 」ボタンを押すことで 1 つずつ動作が確認できます。
「 Call Stack 」などこの記事で詳しい説明はしていないものありますが、なんとなくブラウザママの料理のイメージは掴めればと思います。
:::

### タスク内の一部作業を後回しにしてくれると嬉しいこと

タスク内の一部作業を後回しにしてくれると何が嬉しいのでしょうか？

これについては **もしタスクの実行時間が長かったら...** と考えると、何が嬉しいかわかるでしょう。

ボタンを押したらモーダルが表示されるサイトがあったとしましょう。
ユーザービリティ的にはボタンを押したらすぐモーダルが表示されてほしいです。

ですが、ボタン押下時のタスクの実行時間が長かったらどうでしょうか？
**しばらくモーダルが出てこず、画面はフリーズ** しているように見えます。

これは **ブラウザはタスクが終了しないとユーザーの画面に反映する作業ができない** ためです。
ボタン押下時のタスクの実行時間が長いと、モーダル表示も遅れます。

また、 **タスクの実行時間が長いと、後続の重要なタスクにも影響が出てしまいます**。
ブラウザママの例を思い出してください。
料理以外にも子供の送り迎えという重要なタスクがあった場合、 **料理時間が長すぎたら子供の送り迎えも遅れてしまいます** 。

![block_asobi_boy.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/164159/4cf832f1-0e0c-69e5-6303-37960fcd6307.png)
（ママ、いつまで経っても来ないなぁ）

このようにタスクの実行時間が長いと色々とデメリットが生じます。
**`setTimeout` によって長いタスクを分割し、緊急性のないものはあと回しにすることでこれらのデメリットを抑えることができます** 。

![banzai_kids_boy1.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/164159/c8408df8-69cb-3cdc-482e-8b6556d15a6f.png)

ママが早く迎えにきてくれて子供も大喜びです！

### `setTimeout` を使うべきケース

では、どのような時に `setTimeout` を使えば良いのでしょうか。
**ユーザーの画面に影響のない処理に使う** というケースが考えられます。
最たる例は分析データ送信でしょうか。

```javascript
const button = document.getElementById('button');
button.addEventListener('click', () => {
  const data = updateSomeData();
  showModal(data);
  // 分析データの送信は遅らせる
  setTimeout(sendAnalyticsData, 0)
});
```

この例ではボタンを押下した後、何かしら UI に必要なデータを更新し、モーダルを表示します。
分析データの送信はユーザーの画面に影響が出ない部分なので、 `setTimeout` を使ってタスク分割&遅延させ、ボタンクリック後にすぐモーダルが表示するようにしています。

分析データ送信以外の例としては、データのキャッシュが挙げられます。

```javascript
const cache = new Map();
const button = document.getElementById('button');
button.addEventListener('click', async () => {
  const userInputText = getUserInputText();
  const cachedResult = cache.get(userInputText);
  if (cachedResult) {
    updatePage(cachedResult);
    return;
  }
  const result = await fetchData();
  updatePage(result);
  // データのキャッシュは遅らせる
  setTimeout(() => cache.set(userInputText, result), 0);
});
```

データのキャッシュもはユーザーの画面に影響が出ない部分なので、 `setTimeout` を使ってタスク分割&遅延しています。

ユースケースは色々考えられますが、次のサイトでもいくつか例が紹介されているので、そちらも参考にすると良いでしょう。

https://ja.javascript.info/event-loop

ユーザーの画面に影響のない処理といえど、何がなんでも `setTimeout` を使うと開発者は疲れます😇
また、 `setTimeout` 自体のバッファも生じてしまいます。

目安としてタスクは [50 ms 以内](https://web.dev/i18n/ja/long-tasks-devtools/#:~:text=The%20RAIL%20model%20%E3%81%A7%E3%81%AF%E3%80%81100%20%E3%83%9F%E3%83%AA%E7%A7%92%E4%BB%A5%E5%86%85%E3%81%AB%E8%A6%96%E8%A6%9A%E7%9A%84%E3%81%AA%E5%BF%9C%E7%AD%94%E3%82%92%E5%BE%97%E3%82%8B%E3%81%AB%E3%81%AF%E3%80%81%E3%83%A6%E3%83%BC%E3%82%B6%E3%83%BC%E5%85%A5%E5%8A%9B%E3%82%A4%E3%83%99%E3%83%B3%E3%83%88%E3%82%92%2050%20%E3%83%9F%E3%83%AA%E7%A7%92%E3%81%A7%E5%87%A6%E7%90%86%E3%81%99%E3%82%8B%E3%81%93%E3%81%A8%E3%82%92%E6%8E%A8%E5%A5%A8) が推奨されています。
もしある処理が 50 ms を超えるようであれば `setTimeout` によるタスク分割を検討しても良いでしょう。

### タスクが時間かかっているって、どうすればわかるの？

ではあるタスクが時間がかかっているというのはどうすればわかるのでしょうか。
詳しい解説をするとそれだけで 1 記事作れちゃうので、詳しい説明は割愛しますが、簡単に紹介はしておきます。

Chrome の Developer ツールの「 Perfomance 」タブから確認することができます。

![スクリーンショット 2022-10-22 19.26.58.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/164159/2fda82e6-94ff-fd13-0ccf-273cdeebbb7a.png)

キャプチャの `Start profiling and reload page` を押すと、ページが再読み込みされて、記録が開始されます。
テキトーにボタンなどを押下してみてください。全て記録されます。

![スクリーンショット 2022-10-22 20.22.00.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/164159/d4077213-ce0a-f535-b8a5-4ba264909fdb.png)

キャプチャの `Stop` を押すことで記録が停止し、レポートが表示されます。

![スクリーンショット 2022-10-22 19.34.50.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/164159/37307af9-8738-8b30-1739-530f304b831c.png)

`Main` の部分に着目してみてください。 `Task` というのがたくさん表示されています。
この `Task` にカーソルを当てると、どのくらい実行時間がかかっているかが表示されます。

![スクリーンショット 2022-10-22 19.36.43.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/164159/a9bc9e19-4e1e-958e-bafc-90107644db85.png)

50 ms を超えると赤字で怒られます。
この `Task` について、ライブラリ側（ `React` や `Vue` など)ではなく、自分たちが作っているアプリケーション側の問題であれば、 1 つの対策として `setTimeout` を使うのを検討しても良いでしょう。

Chrome の Developer ツールでの計測を紹介しましたが、簡単にコードを入れることでも確認できます。

```javascript
const button = document.getElementById('button');
button.addEventListener('click', () => {
  // 処理開始時間を記録
  const start = performance.now();
  // 処理
  const data = updateSomeData();
  showModal(data);
  sendAnalyticsData();
  // かかった時間を出力
  console.log(performance.now() - start);
});
```

### 「指定した時間に処理が走るようにする関数」の本当の意味とは...？

冒頭で次のようなコードを紹介しました。

```javascript
function main() {
  console.log('動いたよ！！');
}

// 大体 ３ 秒後に main 関数が動き出す
setTimeout(main, 3000);
```

コメントアウトに「 **大体** ３ 秒後に main 関数が動き出す」と書かれていたことに気づいていたでしょうか？

少し詳しい方なら、「 `setTimeout` は指定した時間には正確には実行されない」ことを知っていたかもしれません。
では **「指定した時間には正確には実行されない」のはなぜでしょうか？**

ここまで記事を読んでいただけた方はなんとなく察しがついているかもしれません。
正確には、 `setTimeout` は指定した時間に処理が走らせるのではなく、 **指定した時間にタスクキューにいれる** のです。

>setTimeout() または setInterval() で作成したタイムアウトまたはインターバルに達すると、対応するコールバックがタスクキューに追加されます。
https://developer.mozilla.org/ja/docs/Web/API/HTML_DOM_API/Microtask_guide

`setTimeout(main, 3000)` の例では、 **3 秒後に `main` 関数がタスクキュー（ TODO リスト）にいれられます** 。
このため、他のタスクで忙しい時は、 **3 秒後に実行されるとは限らないのです** 。

他にも[ 3 秒後にただちに実行されない理由](https://developer.mozilla.org/ja/docs/Web/API/setTimeout#%E9%81%85%E5%BB%B6%E3%81%8C%E6%8C%87%E5%AE%9A%E5%80%A4%E3%82%88%E3%82%8A%E9%95%B7%E3%81%84%E7%90%86%E7%94%B1)はありますが、この `setTimeout` の特性を覚えておくのは JavaScript への理解が深まるのではないでしょうか。

### 4ミリ秒マジック！！

その「ただちに実行されない理由」を 1 つご紹介します。
ここからの話は「へぇー、そうなんだ〜」くらいな気持ちで読んでもらって大丈夫です（読み飛ばしても OK です）。

次のような `setTimeout` を使ったコードがあります。

```javascript
let start = Date.now();
let count = 1;

function test() {
  if (count > 10) {
    alert('終了');
    return;
  }
  const end = Date.now();
  console.log(`${count}回目は${end - start}ms`);
  start = end;
  count++;
  setTimeout(test, 0);
}

setTimeout(test, 0);
```

これを Chrome などのブラウザのコンソールに貼り付けて実行してみてください。
どうなるでしょう？

![スクリーンショット 2022-10-25 9.35.32.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/164159/cb00ce43-0416-e7f7-35ae-ddcb1bbe1219.png)

上のスクリーンショットの例だと 4 回まではほぼ即時で `setTimeout` が実行されていますが、それ以降は 5 ミリ秒かかっています。

次の HTML 仕様書によれば実はブラウザでは **5 つタイマーをネストした後は最低でも 4 ミリ秒の遅延** が発生します。

https://html.spec.whatwg.org/multipage/timers-and-user-prompts.html#timers:~:text=Timers%20can%20be%20nested%3B%20after%20five%20such%20nested%20timers%2C%20however%2C%20the%20interval%20is%20forced%20to%20be%20at%20least%20four%20milliseconds

[このサイト](https://ja.javascript.info/settimeout-setinterval#:~:text=%E3%81%9D%E3%81%AE%E5%88%B6%E9%99%90%E3%81%AF%E5%8F%A4%E3%81%8F%E3%81%8B%E3%82%89%E3%81%82%E3%82%8A%E3%80%81%E5%A4%9A%E3%81%8F%E3%81%AE%E3%82%B9%E3%82%AF%E3%83%AA%E3%83%97%E3%83%88%E3%81%8C%E3%81%9D%E3%82%8C%E3%81%AB%E4%BE%9D%E5%AD%98%E3%81%97%E3%81%A6%E3%81%84%E3%82%8B%E3%81%9F%E3%82%81%E3%80%81%E6%AD%B4%E5%8F%B2%E7%9A%84%E3%81%AA%E7%90%86%E7%94%B1%E3%81%8B%E3%82%89%E5%AD%98%E5%9C%A8%E3%81%97%E3%81%A6%E3%81%84%E3%81%BE%E3%81%99%E3%80%82) によれば歴史的な経緯によるものっぽいですが、いずれにせよ、このような `setTimeout` のマジックも存在するのです。

### 最後に

`setTimeout` の真の力、おわかりいただけたでしょうか？
`setTimeout` は単に「指定した時間に処理が走るようにする関数」というものではなく、深堀りすると面白い事実が出てきます。

難しめな内容ではありますが、初心者の方にもなんとなく理解していただけたら嬉しいです！

`setTimeout` によるパフォーマンス改善の例も挙げましたが、その他にも色々とパフォーマンス改善を紹介しているので、そちらもぜひ参考にしてみてください！

https://qiita.com/nuko-suke/items/50ba4e35289e98d95753

また、私自身もパフォーマンス改善にチャレンジした経験を記事にしているので、そちらもぜひご覧いただけると嬉しいです！

https://qiita.com/nuko-suke/items/22702472543bfd3e585f

最後に [Twitter](https://twitter.com/nuko_suke_dev) もよかったらフォローお願いします！

ここまで記事を閉じずにご覧いただきありがとうございました！

## 参考サイト

https://developer.mozilla.org/ja/docs/Web/API/HTML_DOM_API/Microtask_guide

https://ja.javascript.info/event-loop

https://developer.mozilla.org/ja/docs/Web/JavaScript/EventLoop

https://engineering.mercari.com/blog/entry/20220128-3a0922eaa4/

https://web.dev/optimize-long-tasks/

https://web.dev/i18n/ja/long-tasks-devtools/