ユーザー入力にすばやく反応するウェブサイトの構築は、ウェブ パフォーマンスにおいて最も困難な側面の一つです。Chrome チームは、ウェブ デベロッパーがこれに対応できるよう懸命に取り組んできました。ちょうど今年、Interaction to Next Paint(INP)指標が試験運用版から保留ステータスに移行することが発表されました。First Input Delay(FID)に代わって、2024 年 3 月に Core Web Vitals に組み込まれる予定です。
Chrome チームは現在、ウェブ デベロッパーがウェブサイトをより快適に操作できるようにするための新しい API を提供する継続的な取り組みの一環として、Chrome のバージョン 115 以降で scheduler.yield
のオリジン トライアルを実施しています。scheduler.yield
は、スケジューラ API に新たに提案されたもので、これまで依存してきたメソッドよりも、メインスレッドに制御を戻すための簡単で優れた方法を提供します。
減量時
JavaScript は、Run-to-Complete モデルを使用してタスクを処理します。つまり、タスクがメインスレッドで実行されている場合、そのタスクは完了するために必要な時間だけ実行されます。タスクが完了すると、制御はメインスレッドに戻され、メインスレッドはキュー内の次のタスクを処理できるようになります。
タスクが終了しない極端なケース(無限ループなど)を除けば、JavaScript のタスク スケジューリング ロジックには収益は避けられません。実際に起こるのはいつかというだけです。また、早ければ早いほどよいのです。実行に時間がかかりすぎる(正確には 50 ミリ秒を超える)タスクは、時間のかかるタスクとみなされます。
時間のかかるタスクは、ブラウザのユーザー入力に対する応答が遅くなるため、ページの応答性を低下させる原因となります。処理に時間がかかる頻度が高いほど、また実行時間が長くなるほど、ページの表示が遅いと感じたり、ページが完全に壊れていると感じたりする可能性が高まります。
ただし、コードがブラウザでタスクを開始するからといって、そのタスクが終了するまで待機しないとメインスレッドに制御が戻されません。ページのユーザー入力に対する応答性を高めるには、タスクで明示的に tend を生成することで、次の機会にタスクを終了させることができます。これにより、他のタスクは、長いタスクの完了を待つよりも早くメインスレッドで時間を得ることができます。
<ph type="x-smartling-placeholder">明示的に明白な譲歩とは、ブラウザに「これから行う作業には時間がかかる可能性があることを理解しており、ユーザー入力やその他の重要なタスクに応答する前に、その作業のすべてを行わなくてもよい」ということです。開発者のツールボックスに含まれる貴重なツールで、ユーザー エクスペリエンスの改善に大いに役立ちます。
現在の収益戦略の問題
生成の一般的なメソッドでは、タイムアウト値を 0
に設定して setTimeout
を使用します。これは、setTimeout
に渡されたコールバックが、後続の実行のためにキューに入れられる別のタスクに残りの作業を移動するためです。つまり、ブラウザが勝手に処理を実行するのを待つのではなく、「この大きな処理を小さな単位に分割しましょう」ということになります。
ただし、setTimeout
で yield を実行すると、望ましくない副作用が伴います。つまり、明け取りポイントの後の処理がタスクキューの後方に移動します。ユーザー操作によってスケジュールされたタスクは、引き続き本来のとおりキューの先頭に送られますが、明示的に実行した後の残りの作業は、その前にキューに入れられた競合するソースの他のタスクによって、さらに遅延する可能性があります。
実際の動作を確認するには、こちらの Glitch デモをご覧いただくか、以下の埋め込みバージョンでお試しください。このデモは、クリック可能ないくつかのボタンと、その下にあるボックス(タスクの実行時に記録される)で構成されています。ページが表示されたら、次の操作を行います。
- 上部にある [タスクを定期的に実行する] ボタンをクリックします。これにより、ブロックするタスクが頻繁に実行されるようスケジュールが設定されます。このボタンをクリックすると、タスクログに「Ran block task with
setInterval
」というメッセージがいくつか表示されます。 - 次に、[ループを実行(反復処理のたびに
setTimeout
)] というボタンをクリックします。
デモの下部にあるボックスに次のようなメッセージが表示されます。
Processing loop item 1
Processing loop item 2
Ran blocking task via setInterval
Processing loop item 3
Ran blocking task via setInterval
Processing loop item 4
Ran blocking task via setInterval
Processing loop item 5
Ran blocking task via setInterval
Ran blocking task via setInterval
この出力は「タスクキューの終わり」を示していますsetTimeout
で出力するときに発生する動作。実行するループは 5 つのアイテムを処理し、各アイテムが処理されると setTimeout
を生成します。
これはウェブでよく見られる問題を示しています。スクリプト(特にサードパーティのスクリプト)では、一定の間隔で動作するタイマー関数を登録することは珍しくありません。「タスクキューの終わり」setTimeout
による yielding に伴う動作では、他のタスクソースからの処理が、ループが yied 後に行う必要がある残りの処理よりも先にキューに入れられる可能性があります。
アプリケーションによっては、これが望ましい結果となる場合もあれば、望ましくない結果になる場合もありますが、多くの場合、この動作が原因で、デベロッパーはメインスレッドの制御をすぐに手放したくないと感じることがあります。ユーザー インタラクションの方が早く実行できるというメリットもありますが、ユーザー インタラクション以外の他の作業もメインスレッドで時間を確保できます。これは現実的な問題ですが、scheduler.yield
が解決を手助けしてくれます。
「scheduler.yield
」と入力します。
Chrome のバージョン 115 以降、scheduler.yield
は試験運用版のウェブ プラットフォーム機能としてフラグが立てられていました。「setTimeout
がすでに特別な関数をすでに実行しているのに、なぜ特別な関数が必要なのか」という疑問が湧くことでしょう。
なお、yyd は setTimeout
の設計目標ではなく、むしろコールバックをスケジューリングする際に(タイムアウト値 0
を指定しても)良い副作用です。ただし、setTimeout
で収益を生成すると、残りの作業がタスクキューの後ろに送られることに注意が必要です。デフォルトでは、scheduler.yield
は残りの作業をキューの先頭に送信します。つまり、yielding の直後に再開しようとしていた作業が、他のソースのタスクに後れをとられることはありません(ユーザーの操作は例外です)。
scheduler.yield
はメインスレッドに降り込み、呼び出されると Promise
を返す関数です。つまり、async
関数で await
できます。
async function yieldy () {
// Do some work...
// ...
// Yield!
await scheduler.yield();
// Do some more work...
// ...
}
scheduler.yield
の動作を確認するには、次の操作を行います。
chrome://flags
に移動します。- [試験運用版ウェブ プラットフォーム機能] テストを有効にします。この操作を行った後に、Chrome の再起動が必要になる場合があります。
- デモページに移動するか、このリストの下にあるデモページの埋め込みバージョンを使用してください。
- 上部にある [Run tasks common] ボタンをクリックします。
- 最後に、[ループを実行、反復処理のたびに
scheduler.yield
を生成する] ボタンをクリックします。
ページの下部にあるボックスの出力は次のようになります。
Processing loop item 1
Processing loop item 2
Processing loop item 3
Processing loop item 4
Processing loop item 5
Ran blocking task via setInterval
Ran blocking task via setInterval
Ran blocking task via setInterval
Ran blocking task via setInterval
Ran blocking task via setInterval
setTimeout
を使用して生成するデモとは異なり、ループは反復処理のたびに生成されますが、残りの処理がキューの最後ではなく、キューの前に送られることがわかります。これにより、両方の利点を享受できます。つまり、ウェブサイトでの入力の応答性を向上させると同時に、yielding の後に終了したい処理が遅延しないようにできます。
ぜひお試しください。
scheduler.yield
に関心があり、試したい場合は、Chrome バージョン 115 以降、次の 2 つの方法で行うことができます。
scheduler.yield
をローカルでテストする場合は、Chrome のアドレスバーに「chrome://flags
」と入力して [試験運用版のウェブ プラットフォーム機能] のプルダウンで [有効にする] を選択します。これにより、scheduler.yield
(およびその他の試験運用版の機能)は Chrome のインスタンスでのみ利用可能になります。- 一般公開されているオリジンで実際の Chromium ユーザーが
scheduler.yield
を有効にするには、scheduler.yield
オリジン トライアルに登録する必要があります。これにより、提案された機能を一定期間安全にテストでき、Chrome チームはそれらの機能が現場でどのように使用されているかについて貴重な分析情報を得ることができます。オリジン トライアルの仕組みについて詳しくは、こちらのガイドをご覧ください。
scheduler.yield
をどのように使用するかは、目標によって異なります。ただし、それを実装していないブラウザも引き続きサポートされます。公式のポリフィルを使用できます。ポリフィルは、次の状況に当てはまる場合に便利です。
- タスクのスケジュール設定に、すでにアプリケーションで
scheduler.postTask
を使用しています。 - タスクを設定し、優先順位を付ける必要があります。
scheduler.postTask
API が提供するTaskController
クラスを使用して、タスクをキャンセルしたり、優先順位を変更したりできるようにしたいと考えています。
ご自分の状況に当てはまらない場合は、ポリフィルが適していない可能性があります。その場合、いくつかの方法で独自のフォールバックをロールバックできます。最初のアプローチでは、使用可能であれば scheduler.yield
を使用し、使用できない場合は setTimeout
にフォールバックします。
// A function for shimming scheduler.yield and setTimeout:
function yieldToMain () {
// Use scheduler.yield if it exists:
if ('scheduler' in window && 'yield' in scheduler) {
return scheduler.yield();
}
// Fall back to setTimeout:
return new Promise(resolve => {
setTimeout(resolve, 0);
});
}
// Example usage:
async function doWork () {
// Do some work:
// ...
await yieldToMain();
// Do some other work:
// ...
}
この処理は可能ですが、ご想像のとおり、scheduler.yield
をサポートしていないブラウザでは「キューの先頭」なしで生成されます。確認します。これをまったく行わない場合は、scheduler.yield
を使用できる場合は使用し、使用できない場合はまったく譲らない別の方法を試します。
// A function for shimming scheduler.yield with no fallback:
function yieldToMain () {
// Use scheduler.yield if it exists:
if ('scheduler' in window && 'yield' in scheduler) {
return scheduler.yield();
}
// Fall back to nothing:
return;
}
// Example usage:
async function doWork () {
// Do some work:
// ...
await yieldToMain();
// Do some other work:
// ...
}
スケジューラ API に scheduler.yield
が加わったことで、デベロッパーが現在の収益戦略よりも応答性を簡単に改善できるようになることを期待しています。scheduler.yield
が便利な API だと思われる場合は、Google の調査に参加して改善し、フィードバックをお寄せください。
Unsplash のヒーロー画像(Jonathan Allison)