ユーザー入力にすばやく反応するウェブサイトの構築は、ウェブ パフォーマンスにおいて最も困難な側面の一つです。Chrome チームは、ウェブ デベロッパーがこれを実現できるよう懸命に取り組んできました。今年、Interaction to Next Paint(INP)指標が試験運用ステータスから保留ステータスに移行することが発表されました。2024 年 3 月に、Core Web Vitals として First Input Delay(FID)に代わる指標としてリリースされる予定です。
ウェブデベロッパーがウェブサイトをできるだけ高速に作成できるようにする新しい API の提供に継続的に取り組んでいる Chrome チームは、現在、Chrome バージョン 115 以降で scheduler.yield
のオリジン トライアルを実施しています。scheduler.yield
は、スケジューラ API に新たに提案されたものです。これまで依存してきたメソッドよりも、メインスレッドに制御を戻すための簡単で優れた方法を提供します。
譲渡時
JavaScript は、完了まで実行するモデルを使用してタスクを処理します。つまり、タスクがメインスレッドで実行されると、そのタスクは、完了するために必要な時間だけ実行されることになります。タスクが完了すると、制御はメインスレッドに戻され、メインスレッドはキュー内の次のタスクを処理できるようになります。
タスクが完了しない極端なケース(無限ループなど)を除き、JavaScript のタスク スケジューリング ロジックでは、yield は避けられません。必ず発生します。問題はいつ発生するかです。早ければ早いほど良いでしょう。実行に時間がかかりすぎる(正確には 50 ミリ秒を超える)場合、そのタスクは時間のかかるタスクとみなされます。
長いタスクは、ブラウザがユーザー入力に応答する能力を遅らせるため、ページの応答性が低下する原因となります。長時間のタスクが発生する頻度が高く、実行時間が長くなるほど、ページが遅い、または完全に機能していないとユーザーが感じる可能性が高くなります。
ただし、コードがブラウザでタスクを開始したからといって、そのタスクが完了するまで待ってからメインスレッドに制御を戻す必要はありません。ページのユーザー入力に対する応答性を高めるには、タスクで明示的に tibt を生成して、次の機会にタスクを終了させます。これにより、長時間のタスクの完了を待機するよりも、他のタスクがメインスレッドで時間を早く確保できるようになります。
明示的に譲渡すると、ブラウザに「これから行う処理に時間がかかる可能性があることを理解しています。ユーザー入力や他の重要なタスクに応答する前に、その処理をすべて行う必要はありません」と伝えます。デベロッパーのツールボックスに含まれる貴重なツールで、ユーザー エクスペリエンスの改善に大いに役立ちます。
現在の収益化戦略の問題
一般的な yield 方法は、タイムアウト値が 0
の setTimeout
を使用することです。これは、setTimeout
に渡されたコールバックが、後続の実行のためにキューに入れられる別のタスクに残りの作業を移動するためです。ブラウザが自動的に処理を中断するのを待つのではなく、「この大きな作業を小さな部分に分割しましょう」と指示します。
ただし、setTimeout
で降伏すると、望ましくない副作用が発生する可能性があります。降伏ポイントの後に続く処理は、タスクキューの後方に移動します。ユーザー操作によってスケジュールされたタスクは、本来どおりキューの先頭に移動しますが、明示的に譲渡した後に実行する残りの処理は、競合するソースからの他のタスクが先にキューに追加されたために、さらに遅延する可能性があります。
実際に試すには、こちらの Glitch デモをお試しください。または、以下の埋め込みバージョンで試すこともできます。このデモは、クリックできるボタンがいくつかあり、その下にタスクの実行時間が記録されるボックスがあります。ページに移動したら、次の操作を行います。
- 上部の [タスクを定期的に実行] ボタンをクリックすると、ブロック タスクが定期的に実行されるようにスケジュールされます。このボタンをクリックすると、タスクログに「
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
で yield すると「タスクキューの終了」動作が発生します。つまり、他のタスクソースからの処理が、yield 後にループが行う残りの処理よりも先にキューに追加される可能性があります。
これは、アプリケーションによって望ましい結果である場合とそうでない場合がありますが、多くの場合、この動作が原因で、デベロッパーはメインスレッドの制御を簡単に放棄することをためらうことがあります。ユーザー インタラクションの方が早く実行できるというメリットもありますが、ユーザー インタラクション以外の他の作業もメインスレッドで時間を確保できます。確かに問題ですが、scheduler.yield
で解決できます。
「scheduler.yield
」と入力します。
scheduler.yield
は、Chrome バージョン 115 以降、試験運用版のウェブ プラットフォームの機能としてフラグ制限付きで利用可能でした。「setTimeout
がすでに処理を中断しているのに、特別な関数で処理を中断する必要があるのはなぜですか?」という疑問が湧くかもしれません。
なお、yield は setTimeout
の設計目標ではなく、0
のタイムアウト値が指定されている場合でも、コールバックを後で実行するようにスケジュールする際の副作用です。ただし、setTimeout
で yield すると、残りの処理がタスクキューの後方に送信されることを覚えておくことが重要です。デフォルトでは、scheduler.yield
は残りの処理をキューの先頭に送信します。つまり、譲渡直後に再開したい処理が、他のソースからのタスクに後回しされることはありません(ユーザー操作は例外です)。
scheduler.yield
は、メインスレッドに譲渡し、呼び出されると Promise
を返す関数です。つまり、async
関数で await
できます。
async function yieldy () {
// Do some work...
// ...
// Yield!
await scheduler.yield();
// Do some more work...
// ...
}
scheduler.yield
の動作を確認する手順は次のとおりです。
chrome://flags
に移動します。- 試験運用版のウェブ プラットフォーム機能のテストを有効にします。この操作を行った後、Chrome を再起動しなければならない場合があります。
- デモページに移動するか、このリストの下にある埋め込みバージョンを使用します。
- 上部の [タスクを定期的に実行] ボタンをクリックします。
- 最後に、[ループを実行し、各反復処理で
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
を使用して yield するデモとは異なり、このループは、イテレーションのたびに yield を実行しても、残りの処理をキューの後端ではなくキューの先頭に送信します。これにより、両方の利点が得られます。ウェブサイトの入力応答性を向上させるために譲渡できますが、譲渡後に完了する予定だった処理が遅延することもありません。
ぜひお試しください。
scheduler.yield
に興味をお持ちで、試してみたいと思われる場合は、Chrome バージョン 115 以降で次の 2 つの方法で試すことができます。
scheduler.yield
をローカルでテストする場合は、Chrome のアドレスバーに「chrome://flags
」と入力して [試験運用版のウェブ プラットフォーム機能] のプルダウンで [有効にする] を選択します。これにより、scheduler.yield
(および他の試験運用版機能)は、自分の Chrome インスタンスでのみ使用できるようになります。- 一般公開されているオリジンで実際の Chromium ユーザーに対して
scheduler.yield
を有効にするには、scheduler.yield
オリジン トライアルに登録する必要があります。これにより、提案された機能を一定期間安全にテストできます。また、Chrome チームは、これらの機能が現場でどのように使用されているかについて貴重な分析情報を得ることができます。オリジン トライアルの仕組みについて詳しくは、こちらのガイドをご覧ください。
scheduler.yield
を実装していないブラウザをサポートしながら scheduler.yield
を使用する方法は、目標によって異なります。公式のポリフィルを使用できます。ポリフィルは、次のような場合に役立ちます。
- すでにアプリで
scheduler.postTask
を使用してタスクのスケジュール設定を行っています。 - タスクと譲渡の優先度を設定したい場合。
scheduler.postTask
API が提供するTaskController
クラスを使用して、タスクをキャンセルまたは優先度を変更できるようにします。
状況がこれに当てはまらない場合は、ポリフィルは適切ではない可能性があります。その場合は、いくつかの方法で独自の代替手段をロールアウトできます。最初の方法では、scheduler.yield
が使用可能な場合は 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
をサポートしていないブラウザは「キューの先頭」動作なしで 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:
// ...
}
scheduler.yield
は、スケジューラ API に追加された新機能です。これにより、デベロッパーは現在の yielding 戦略よりも簡単に応答性を改善できるようになります。scheduler.yield
が有用な API と思われる場合は、改善に役立つ調査にご参加ください。また、さらなる改善方法についてフィードバックをお寄せください。
Unsplash のヒーロー画像(Jonathan Allison 撮影)