scheduler.yield オリジン トライアルのご紹介

ユーザー入力にすばやく反応するウェブサイトの構築は、ウェブ パフォーマンスにおいて最も困難な側面の一つです。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">
</ph> タスクを分割することで入力の応答性が向上する仕組みの描写。上部にある長いタスクは、タスクが完了するまでイベント ハンドラの実行をブロックします。タスクの下部では、チャンクされたタスクにより、イベント ハンドラを本来よりも早く実行できるようになっています。 <ph type="x-smartling-placeholder">
</ph> 制御をメインスレッドに戻すことの可視化。一番上に言えば、明け渡しはタスクが完了まで実行された後にのみ行われます。つまり、制御がメインスレッドに戻るまで、タスクの完了に時間がかかることがあります。一番下では、明暗が明示的に実行され、長いタスクがいくつかの小さなタスクに分割されます。これにより、ユーザー操作が迅速に実行できるようになり、入力の応答性と INP が向上します。

明示的に明白な譲歩とは、ブラウザに「これから行う作業には時間がかかる可能性があることを理解しており、ユーザー入力やその他の重要なタスクに応答する前に、その作業のすべてを行わなくてもよい」ということです。開発者のツールボックスに含まれる貴重なツールで、ユーザー エクスペリエンスの改善に大いに役立ちます。

現在の収益戦略の問題

生成の一般的なメソッドでは、タイムアウト値を 0 に設定して setTimeout を使用します。これは、setTimeout に渡されたコールバックが、後続の実行のためにキューに入れられる別のタスクに残りの作業を移動するためです。つまり、ブラウザが勝手に処理を実行するのを待つのではなく、「この大きな処理を小さな単位に分割しましょう」ということになります。

ただし、setTimeout で yield を実行すると、望ましくない副作用が伴います。つまり、明け取りポイントの後の処理がタスクキューの後方に移動します。ユーザー操作によってスケジュールされたタスクは、引き続き本来のとおりキューの先頭に送られますが、明示的に実行した後の残りの作業は、その前にキューに入れられた競合するソースの他のタスクによって、さらに遅延する可能性があります。

実際の動作を確認するには、こちらの Glitch デモをご覧いただくか、以下の埋め込みバージョンでお試しください。このデモは、クリック可能ないくつかのボタンと、その下にあるボックス(タスクの実行時に記録される)で構成されています。ページが表示されたら、次の操作を行います。

  1. 上部にある [タスクを定期的に実行する] ボタンをクリックします。これにより、ブロックするタスクが頻繁に実行されるようスケジュールが設定されます。このボタンをクリックすると、タスクログに「Ran block task with setInterval」というメッセージがいくつか表示されます。
  2. 次に、[ループを実行(反復処理のたびに 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 の動作を確認するには、次の操作を行います。

  1. chrome://flags に移動します。
  2. [試験運用版ウェブ プラットフォーム機能] テストを有効にします。この操作を行った後に、Chrome の再起動が必要になる場合があります。
  3. デモページに移動するか、このリストの下にあるデモページの埋め込みバージョンを使用してください。
  4. 上部にある [Run tasks common] ボタンをクリックします。
  5. 最後に、[ループを実行、反復処理のたびに 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 つの方法で行うことができます。

  1. scheduler.yield をローカルでテストする場合は、Chrome のアドレスバーに「chrome://flags」と入力して [試験運用版のウェブ プラットフォーム機能] のプルダウンで [有効にする] を選択します。これにより、scheduler.yield(およびその他の試験運用版の機能)は Chrome のインスタンスでのみ利用可能になります。
  2. 一般公開されているオリジンで実際の Chromium ユーザーが scheduler.yield を有効にするには、scheduler.yield オリジン トライアルに登録する必要があります。これにより、提案された機能を一定期間安全にテストでき、Chrome チームはそれらの機能が現場でどのように使用されているかについて貴重な分析情報を得ることができます。オリジン トライアルの仕組みについて詳しくは、こちらのガイドをご覧ください。

scheduler.yield をどのように使用するかは、目標によって異なります。ただし、それを実装していないブラウザも引き続きサポートされます。公式のポリフィルを使用できます。ポリフィルは、次の状況に当てはまる場合に便利です。

  1. タスクのスケジュール設定に、すでにアプリケーションで scheduler.postTask を使用しています。
  2. タスクを設定し、優先順位を付ける必要があります。
  3. 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