isInputPending() を使用した JS スケジューリングの改善

読み込みパフォーマンスと入力応答性のトレードオフの回避に役立つ可能性のある新しい JavaScript API。

Nate Schloss
Nate Schloss
Andrew Comminos
Andrew Comminos

読み込みが速いのは簡単ではありません。現在、JS を利用してコンテンツをレンダリングするサイトでは、読み込みのパフォーマンスと入力の応答性のトレードオフを考慮する必要があります。つまり、表示に必要なすべての作業を一度に実行するか(読み込みのパフォーマンスと入力の応答性の低下)、入力とペイントの応答性を維持するために、作業を小さなタスクに分割します(読み込みのパフォーマンスと入力の応答性が低い)。

このトレードオフをなくすために、Facebook は成果を出さずに応答性を向上させるために、Chromium に isInputPending() API を提案し、実装しました。オリジン トライアルのフィードバックに基づき、API にいくつかの更新を加えました。このたび、Chromium 87 では API がデフォルトでリリースされるようになりました。

ブラウザの互換性

対応ブラウザ

  • 87
  • 87
  • x
  • x

isInputPending() はバージョン 87 以降の Chromium ベースのブラウザで提供されています。 この API を提供する意思を示すブラウザは他にありません。

背景

現在の JS エコシステムのほとんどの処理は、メインスレッドという 1 つのスレッドで実行されます。これによりデベロッパーは堅牢な実行モデルになりますが、スクリプトを長時間実行すると、ユーザー エクスペリエンス(特に応答性)が著しく低下する可能性があります。たとえば、入力イベントの発生中にページで大量の処理が行われている場合、その処理が完了するまで、ページでクリック入力イベントが処理されません。

現在のベスト プラクティスは、JavaScript をより小さなブロックに分割することでこの問題に対処することです。ページの読み込み中に、そのページで少量の JavaScript が実行され、その後に制御が渡されてブラウザに制御が戻されます。これにより、ブラウザは入力イベントキューをチェックして、ページに伝える必要があるものがあるかどうかを確認できます。その後、JavaScript ブロックが追加されると、ブラウザは実行に戻って実行できるようになります。これは役立ちますが、他の問題が発生する可能性があります。

ページからブラウザに制御が渡されるたびに、ブラウザが入力イベントキューを確認し、イベントを処理して、次の JavaScript ブロックを取得するまでに時間がかかります。ブラウザはイベントにすばやく応答しますが、ページの全体的な読み込み時間は遅くなります。読み込み頻度が高すぎると ページの読み込みが遅くなります生成頻度が下がると、ブラウザがユーザー イベントに応答するまでの時間が長くなり、ユーザーは不満を抱きます。楽しくない。

長時間の JS タスクを実行すると、ブラウザでイベントをディスパッチする時間が短くなることを示す図。

Facebook では、この煩わしいトレードオフを解消できる新しい読み込み方法を考え出せたらどうなるか考えていました。この件について Chrome の仲間に伝え、isInputPending() の提案を思いつきました。isInputPending() API は、ウェブでのユーザー入力に割り込みというコンセプトを最初に使用した API です。これにより、JavaScript でブラウザが関与することなく入力をチェックできるようになります。

isInputPending() は、ブラウザへの実行を完全に譲ることなく、JS が保留中のユーザー入力があるかどうかを確認できることを示す図です。

API への関心があったため、Google は Chrome の同僚と協力して、Chromium に機能を実装し、リリースしました。Google は Chrome のエンジニアの協力を得て、オリジン トライアル(API を完全にリリースする前に、Chrome で変更をテストしてデベロッパーからフィードバックを得るための方法です)にパッチを適用することができました。

オリジン トライアルと W3C ウェブ パフォーマンス ワーキング グループの他のメンバーからのフィードバックを受け、API の変更を実装しました。

例: yieldier スケジューラ

たとえば、コンポーネントからのマークアップの生成、プライムの因数分解、クールな読み込みスピナーの描画など、ページを読み込むためにディスプレイをブロックする処理が大量にあるとします。それぞれが個別の作業アイテムに分割されます。スケジューラ パターンを使用して、架空の processWorkQueue() 関数で処理をどのように処理するかについて考えてみましょう。

const DEADLINE = performance.now() + QUANTUM;
while (workQueue.length > 0) {
  if (performance.now() >= DEADLINE) {
    // Yield the event loop if we're out of time.
    setTimeout(processWorkQueue);
    return;
  }
  let job = workQueue.shift();
  job.execute();
}

後ほど setTimeout() を介して新しいマクロタスク内で processWorkQueue() を呼び出すことで、比較的中断せずに実行しながら、ブラウザで入力に対する応答性(作業の再開前にイベント ハンドラを実行できる)を維持できるようになります。ただし、イベントループの制御を必要とする他の処理によって長時間スケジュールが解除されたり、イベント レイテンシが最大で QUANTUM ミリ秒増加したりする可能性があります。

問題ございませんが、改善できる点はありますか?もちろん処理できます。

const DEADLINE = performance.now() + QUANTUM;
while (workQueue.length > 0) {
  if (navigator.scheduling.isInputPending() || performance.now() >= DEADLINE) {
    // Yield if we have to handle an input event, or we're out of time.
    setTimeout(processWorkQueue);
    return;
  }
  let job = workQueue.shift();
  job.execute();
}

navigator.scheduling.isInputPending() の呼び出しを導入することで、入力への応答が速くなり、ディスプレイ ブロッキング処理が中断されることがなくなります。処理が完了するまで入力以外の処理(描画など)を行わない場合は、QUANTUM の長さも簡単に増やすことができます。

デフォルトでは、「連続」イベントは isInputPending() から返されません。これには、mousemovepointermove などがあります。収益にも興味がある場合は問題ありませんincludeContinuoustrue に設定してオブジェクトを isInputPending() に提供すると、次のようになります。

const DEADLINE = performance.now() + QUANTUM;
const options = { includeContinuous: true };
while (workQueue.length > 0) {
  if (navigator.scheduling.isInputPending(options) || performance.now() >= DEADLINE) {
    // Yield if we have to handle an input event (any of them!), or we're out of time.
    setTimeout(processWorkQueue);
    return;
  }
  let job = workQueue.shift();
  job.execute();
}

これで、React などのフレームワークでは、同様のロジックを使用して、コア スケジューリング ライブラリに isInputPending() サポートを構築しています。これにより、これらのフレームワークを使用するデベロッパーが、大幅な書き換えをしなくても舞台裏で isInputPending() のメリットを享受できるようになることが期待されます。

収益は必ずしも悪いことではない

収益が少なくなることが、すべてのユースケースに適したソリューションというわけではありません。レンダリングの実行やページ上の他のスクリプトの実行など、入力イベントの処理以外に、ブラウザに制御を戻す理由はたくさんあります。

ブラウザが保留中の入力イベントを適切に関連付けられない場合があります。特に、クロスオリジンの iframe で複雑なクリップやマスクを設定すると、偽陰性が報告される可能性があります(これらのフレームをターゲットにすると、isInputPending() が予期せず false を返す可能性があります)。サイトでスタイル設定されたサブフレームとのやり取りが必要な場合は、十分な頻度で収益が発生していることを確認してください。

イベントループを共有する他のページにも注意してください。Chrome for Android などのプラットフォームでは、複数のオリジンがイベントループを共有することはよくあります。入力がクロスオリジン フレームにディスパッチされる場合、isInputPending()true を返すことはありません。そのため、バックグラウンドのページによってフォアグラウンド ページの応答性が損なわれる可能性があります。Page Visibility API を使用すると、バックグラウンドで作業を行う際に、頻度を減らしたり、延期したり、収益を増やしたりできます。

isInputPending() は慎重に使用することをおすすめします。ユーザーをブロックする作業がない場合は、イベントループの他のユーザーへの優しさを保つため、作業の負担を増やしてください。長いタスクは有害な場合があります

フィードバック

  • 仕様に関するフィードバックは、is-input-pending リポジトリに残してください。
  • Twitter で @acomminos(仕様作成者の一人)にお問い合わせください。

おわりに

isInputPending() がリリースされ、デベロッパーの皆様は今すぐ利用できるようになりました。この API は、Facebook が新しいウェブ API を構築し、アイデアの育成から標準提案へと移行し、実際にブラウザにデプロイするまでの初めての API です。ここまでの道のりに協力してくれたすべての人に感謝するとともに、このアイデアを具体化してリリースしてくれた Chrome の皆さんに特別な感謝の言葉を申し上げます。

ヒーロー写真(作成者: Will H McMahan、出典: Unsplash