179
158

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 1 year has passed since last update.

【JavaScript】「お手すきの時にご確認お願いします」ができる便利なブラウザ API

Last updated at Posted at 2022-11-06

こんにちは。ぬこすけです。

最近(2022/10/24)、 setTimeout について記事を書いたのですが、予想以上に多くの人に読んでいただけて嬉しい限りです。

setTimeout によって処理を遅らせることは皆さんご存知かと思います。
(上の記事に詳しい仕組みは解説しているので、ぜひ一読ください)

ただ、 setTimeout にも問題はあります。
それは 処理の優先度をブラウザに教えることができない 問題です。

setTimeout はただ処理を後回しにしているにすぎません。
後回しにするにしても「この処理は優先度が低いので、ひまな時にやってね」みたいに詳しくブラウザに教えることができません。
パフォーマンスの観点では、重要な処理は優先的にさばいてより快適なユーザー体験を提供したいところです。

この問題を解決するためにあるブラウザ API が生まれました。
今回はこのブラウザ API を紹介したいと思います!

本来は少し難しい内容ですが、初心者の方にもわかりやすく説明したいと思います

そもそもブラウザ API とは?

本題に入る前に、簡単にブラウザ API について説明しましょう。

ブラウザー API — JavaScript 言語の上に乗って、より簡単に機能を実装するためにブラウザーに組み込まれた仕組みです。
https://developer.mozilla.org/ja/docs/Learn/JavaScript/Client-side_web_APIs/Introduction

だそうです😋

これだけだとあっさりしているのでもう少し説明しましょう。

よく JavaScript のコードで console.logsetTimeout などを見かけると思います。
console.log であればコンソールにログを出力し、 setTimeout であれば指定した時間まで処理を遅らせます。

これらは JavaScript のコードではありますが、正確には JavaScript がコンソールにログを出力をしているわけでも、指定した時間まで処理を遅らせているわけではありません

boy_question.png

「じゃあ誰やねん」という話です。

ブラウザ API」と言うくらいなので、 ブラウザ です。
console.logsetTimeout などは ブラウザ側で用意された機能 なんです。

JavaScript で普段何気なく console.logsetTimeout を書いていますが、裏ではブラウザがお仕事してくれていたんです。

もちろん、 console.logsetTimeout 以外にもブラウザ側で用意された機能はたくさんあります。
今回はその 1 つである requestIdleCallback について説明 します。

requestIdleCallback とは?

後回しにするにしても「この処理は優先度が低いので、ひまな時にやってね」みたいに詳しくブラウザに教えることができません。

冒頭に setTimeout の問題をお話しました。
この問題を解決するのが requestIdleCallback です。
「ひまな時にやってね」ができるのです

ブラウザは JavaScript を実行したり、画面に描画したり、基本忙しいです。

document_hanko_bunka.png

そんなブラウザですが、ひまになる時(アイドル状態とも言います)があります。
というのも、ブラウザは決められた期間ごと(フレームと呼んだりします)で仕事をするので、早く仕事が終わればひまになるのです。

例えば、工場で働いていたとして、 10 分おきにベルトコンベアから製品が流れてきて、品質をチェックをしたり、梱包したりなどの仕事をするとしましょう。

job_syokuhin_koujou_apron.png

意外も不良品もなく、梱包もサクッと終わり、 7 分で仕事が終わりました。
次の製品が来るまでの 3 分間ひまです。
この 3 分間に他の作業をお願いするのが requestIdleCallback です

syorui_morau_woman.png

requestIdleCallback のイメージをつかんだところで、具体的なコードを見てみましょう。

requestIdleCallback のコード例

const hoge = () => console.log('ブラウザがひまな時に実行!');
requestIdleCallback(hoge);

ブラウザがひまな時に実行したい処理を requestIdleCallback に渡してあげるだけで終わり です。
楽ちんです。

ただ、 1 つ問題が起こる場合があります。
requestIdleCallback に登録する処理ですが、 必ずしも実行されるとは限りません

あくまでもブラウザがひまな時にしか実行されないので、ずっと忙しいと実行されません。
ですが、 もし必ず実行させたいのであれば方法があります

requestIdleCallback には第二引数に timeout を指定することができます。

const hoge = () => console.log('ブラウザがひまな時に実行!');
requestIdleCallback(hoge, { timeout: 3000 });

上の例ではブラウザが忙しく、 3000 ミリ秒(= 3 秒) 以内に指定した処理が実行されなかった場合、 setTimeout のように hoge がタスクキューに詰まれて実行されます。
タスクキューについてのお話は次の記事をご参考ください。

なので、 たとえブラウザが忙しくて処理を実行できない場合でも、 timeout の指定した時間によって実行が保証 されます。

-- 余談 -----------------------
余談なので読み飛ばしても大丈夫です。
日本語訳の MDN には次の記述がありますが、太字の部分は誤りです。処理が実行されずに timeout で指定した時間を超えたら、次のアイドル期間(ひまな期間)に実行されるのではなく、 setTimeout のようにタスクキューに積まれます。

timeout:timeout に正の値が指定され、かつコールバックがその値の期間(ミリ秒)内に実行されていない場合、 コールバックは次のアイドル期間に実行 されます。
https://developer.mozilla.org/ja/docs/Web/API/Window/requestIdleCallback

もし詳しく知りたい方は英語の MDNW3C のドキュメント をご覧ください。
-- 余談 終わり ------------------

余談から話を戻しましょう。

今までの話を整理すると、 requestIdleCallback による処理の実行は 2 パターンあるようです。

  1. ひまな時に実行される
  2. timeout で指定した時間後に実行される

この 2 パターンのどちらで実行されたかは見分けることができます

先ほどのコード例をもう一度見てみましょう。

const hoge = () => console.log('ブラウザがひまな時に実行!');
requestIdleCallback(hoge, { timeout: 3000 });

requestIdleCallback の第一引数に hoge 関数を渡しています。
実は requestIdleCallback はこの引数に指定した関数に詳細な情報を渡します

このコードを少し書き換えてみます。

const hoge = (deadline) => {
  if (deadline.timeRemaining() > 0) {
    console.log('ひまな時に実行!');
  }
  if (deadline.didTimeout) {
    console.log('タイムアウトによる実行!');
  }
};

requestIdleCallback(hoge, { timeout: 3000 });

hoge 関数の引数に deadline という変数が入っています。
なんでしょう、これ?

boy_question.png

まあ deadline と言うくらいなので、「期日(デッドライン)が迫っているぜ!」的な情報が入っているんでしょう。
コード例に書いた 2 つの if 文を詳しく見ていきます。

if (deadline.timeRemaining() > 0) {
  console.log('ひまな時に実行!');
}

deadline.timeRemaining() は「ひまな時間が残りどれくらいか」を教えてくれます
requestidlecallback とは」 の工場の例では、作業員は 3 分間手すきの時間がありました。
この 3 分が deadline.timeRemaining() で取得できます。

if (deadline.didTimeout) {
  console.log('タイムアウトによる実行!');
}

deadline.didTimeout はそのまんまですが、「タイムアウトしたかどうか」を教えてくれます

requestIdleCallback(hoge, { timeout: 3000 }); は「 3 秒以内に手すきの時間があったら実行、忙しくて無理なら 3 秒後強制実行」という意味ですが、 deadline.didTimeouttrue であれば、「忙しくて処理が実行できずに 3 秒たっちゃったぜ!」ということを表します。

このように requestIdleCallback に渡す関数には 「ひまな時間が残りどれくらいか」「タイムアウトしたかどうか」という情報も知ることができます

requestIdleCallback を使う時の注意点

これから requestIdleCallback と使った実用的な例を紹介する前に、 requestIdleCallback について注意点をいくつか紹介しておきます。

時間のかかる処理を requestIdleCallback に登録しない

時間のかかる処理を登録すると、後続の重要なタスクに影響が出ます

requestidlecallback とは」 の工場の例を思い出してください。
一時的に 3 分間だけひまな作業員に 10 分もかかる仕事を渡したら、次の 3 分後にベルトコンベアからやってくる製品の品質チェックや梱包など重要なお仕事が遅れてしまいます。

なので、 requestIdleCallback に登録する処理はサクッと終わる処理にしましょう。
目安としては 50 ミリ秒 です。
50 ミリ秒以内にすることで、ユーザーの何かしらの操作があっても遅いと感じることなく反応できます。
W3C のドキュメントにも 50 ミリ秒に関して言及があります。

DOM の変更を requestIdleCallback に登録しない

document.body.appendChild などの DOM の変更は、スタイルやレイアウトの計算など、付随して描画のための仕事が増えます。
先ほど「時間のかかる処理を requestIdleCallback に登録しない」でもお話した通り、後続のタスクに影響を与える可能性があります。

もし requestIdleCallback 内で DOM の変更を行いたい場合は requestAnimationFrame を使うことをおすすめします。
この記事では requestAnimationFrame については解説はしませんが、近いうちに requestAnimationFrame も記事にしようかなーと思っているので、もし Twitter かこの記事をストックしてもらえれば通知します!

Safari では requestIdleCallback が使えない

執筆時点(2022/11/04)において、 Sarafi では requestIdleCallback が使えません

ちなみに ポリフィル(ブラウザーでサポートしていない機能を使えるようにするためのコード)もありません
setTimeout を使った ポリフィルもどき ならあります。

Safari も含めて requestIdleCallback を使いたい場合は、あきらめて普通に処理を実行するか、ポリフィルもどきを使うかのどちらかになるでしょう。

if (typeof window !== 'undefined' && window.requestIdleCallback) {
  // requestIdleCallback を使う
} else {
  // 普通に処理を実行するか、ポリフィルもどきのコードを書く
}

requestIdleCallback の使いどころ

優先度の低く、かつ重くない処理 に使えるでしょう。
例として挙げられるのは分析用のデータ送信でしょうか。

const lazySendAnalyticsData = (event) => {
   requestIdleCallback(() => {
      import('./sendAnalyticsData').then(({ default: sendAnalyticsData }) = {
        requestIdleCallback(() => sendAnalyticsData(event));
      })
   });
}

const button = document.getElementById('button');
button.addEventListener('click', lazySendAnalyticsData);

簡単にコード例を書いてみました。
この例ではボタンがクリックされた後、 2 段階の処理に分けられます。

1 段階目は、分析用のモジュール( sendAnalyticsData )の遅延読み込みです。
動的 import を使ってただ遅延的に読み込むのではなく、 requestIdleCallback を使ってブラウザがひまな時に読み込ませます。

2 段階目は、 sendAnalyticsData による分析データの送信です。
1 段階目でモジュールの遅延読み込みをした後の then 句はマイクロタスクとして実行されます(マイクロタスクについては知らなくても OK です)が、
さらに遅延的に分析データの送信を送信させるため、もう一度 requestIdleCallback を使って処理をブラウザがひまな時に実行させるようにします。

このような実装をすることで、他の優先度の高いタスクに処理を譲ることができます。

requestIdleCallback を使ったパフォーマンスチューニング事例

実際にパフォーマンスチューニングとして requestIdleCallback が使われている事例をいくつか紹介しておきます。

例えば、某企業でも requestIdleCallback と使ってパフォーマンスチューニングをしているようです。

私も個人で開発したぬこぷろ というサイトで requestIdleCallback を使ってパフォーマンスチューニングをした経験もあります。

また、ちょっとパフォーマンスチューニングの実例からは逸れちゃうかもしれませんが、 Next.js でも requestIdleCallback が使われていたりします。例えば、 next/script というスクリプトを色々な戦略で読み込ませられる機能がありますが、 requestIdleCallback を使って実装されていたりもします。

終わりに

requestIdleCallback について理解いただけたでしょうか?

requestIdleCallback 自体少し難しいものでもありますし、ドキュメントも少ないので私は理解に苦労しました...
皆さんの理解の助けになれば嬉しいです。
(もし内容に誤りなどありましたらご連絡いただければと思います)

requestIdleCallback はパフォーマンスチューニングのためのものですが、その他にも次の記事でパフォーマンスチューニングのハウツー集を紹介しているので、興味があればぜひこちらもご覧ください!

今後もフロントエンドに関する有益な情報を発信してく予定なので、ぜひ Twitter もフォローよろしくお願いします!

ここまでご覧いただきありがとうございました!

2022/11/16 追記

requestIdleCallback を便利に扱うための npm ライブラリを公開しました!
自動で実行結果をキャッシュ、 TypeScript のサポートなど役立つ機能があります!
ぜひ使ってみてください!バグなどあれば issue まで!

参考

179
158
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
179
158

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?