サービス ワーカーを使用したことがある人なら誰でも、サービス ワーカーは完全に非同期であることを知っています。FetchEvent
などのイベントベースのインターフェースにのみ依存し、プロミスを使用して非同期オペレーションが完了したことを通知します。
サービス ワーカーのフェッチ イベント ハンドラが提供するレスポンスについては、非同期処理も同様に重要です(デベロッパーには見えにくいものの、重要です)。ストリーミング レスポンスは、ここでのゴールドスタンダードです。これにより、最初のデータ チャンクが利用可能になり次第、元のリクエストを行ったページでレスポンスの処理を開始できます。また、ストリーミング用に最適化されたパーサーを使用して、コンテンツを段階的に表示することもできます。
独自の fetch
イベント ハンドラを作成する場合は、fetch()
または caches.match()
を介して取得した Response
(または Response
の Promise)を respondWith()
メソッドに渡して、処理を終了するのが一般的です。幸い、どちらの方法で作成された Response
も、すでにストリーミング可能です。残念ながら、少なくとも現時点では、「手動で」作成された Response
はストリーミングできません。ここで Streams API の出番です。
ストリーム
ストリームは、増分作成と操作が可能なデータソースであり、非同期のデータ チャンクの読み取りまたは書き込み用のインターフェースを提供します。このインターフェースでは、メモリ内で使用可能なデータは、ある時点でのサブセットのみです。ここでは、fetchEvent.respondWith()
に渡される Response
オブジェクトの作成に使用できる ReadableStream
に注目します。
self.addEventListener('fetch', event => {
var stream = new ReadableStream({
start(controller) {
if (/* there's more data */) {
controller.enqueue(/* your data here */);
} else {
controller.close();
}
});
});
var response = new Response(stream, {
headers: {'content-type': /* your content-type here */}
});
event.respondWith(response);
});
fetch
イベントをトリガーしたリクエストのページは、event.respondWith()
が呼び出されるとすぐにストリーミング レスポンスを取得します。また、サービス ワーカーが追加データを enqueue()
し続けている限り、そのストリームから読み取り続けます。サービス ワーカーからページに送信されるレスポンスは完全に非同期であり、ストリームの入力を完全に制御できます。
実際の用途
前の例にはプレースホルダの /* your data here */
コメントがいくつかあり、実際の実装の詳細は軽視されていることがわかります。実際の例を見てみましょう。
Jake Archibald は、ストリームを使用してキャッシュに保存された複数の HTML スニペットから HTML レスポンスをつなぎ合わせ、fetch()
経由でストリーミングされる「ライブ」データ(この場合は彼のブログのコンテンツ)を組み合わせる優れた例を公開しています。
Jake が説明しているように、ストリーミング レスポンスを使用するメリットは、ブログ コンテンツ全体の取得が完了するのを待たずに、キャッシュからすばやく読み込まれる最初のビットを含む、ストリーミングされる HTML をブラウザが解析してレンダリングできることです。これにより、ブラウザのプログレッシブ HTML レンダリング機能を最大限に活用できます。一部の画像形式や動画形式など、段階的にレンダリングできる他のリソースにも、このアプローチを適用できます。
ストリームアプリシェルも同じですか?
サービス ワーカーを使用してウェブアプリを強化する既存のベスト プラクティスは、アプリシェル + 動的コンテンツモデルに重点を置いています。このアプローチでは、ウェブ アプリケーションの「シェル」(構造とレイアウトの表示に必要な最小限の HTML、JavaScript、CSS)を積極的にキャッシュに保存し、クライアントサイド リクエストを介して特定のページに必要な動的コンテンツを読み込みます。
ストリームは、アプリシェル モデルの代替手段です。ユーザーが新しいページに移動すると、より完全な HTML レスポンスがブラウザにストリーミングされます。ストリーミング レスポンスはキャッシュに保存されたリソースを利用できるため、オフラインでも HTML の最初のチャンクをすばやく提供できますが、最終的には従来のサーバー レンダリング レスポンス本文に近づきます。たとえば、ウェブアプリが、部分テンプレートをつなぎ合わせて HTML をサーバー レンダリングするコンテンツ管理システムをベースにしている場合、そのモデルはストリーミング レスポンスを直接使用するように変換され、テンプレート ロジックはサーバーではなくサービス ワーカーに複製されます。次の動画に示すように、このユースケースでは、ストリーミング レスポンスがもたらす速度の利点は非常に顕著です。
HTML レスポンス全体をストリーミングする重要な利点の一つは、動画で最も高速な方法である理由を説明するものです。最初のナビゲーション リクエスト中にレンダリングされた HTML は、ブラウザのストリーミング HTML パーサーを最大限に活用できます。ページの読み込み後にドキュメントに挿入される HTML のチャンクは(アプリシェル モデルで一般的であるように)、この最適化を利用できません。
では、Service Worker の実装の計画段階にある場合、どのモデルを採用すればよいでしょうか。段階的にレンダリングされるストリーミング レスポンスですか?それとも、動的コンテンツのクライアントサイド リクエストと組み合わせた軽量シェルですか?答えは、CMS と部分テンプレートに依存する既存の実装があるかどうか(ストリームの利点)、プログレッシブ レンダリングのメリットが期待される単一の大規模な HTML ペイロードがあるかどうか(ストリームの利点)、ウェブアプリをシングルページ アプリケーションとしてモデル化するのに適しているかどうか(アプリシェルの利点)、複数のブラウザの安定版リリースで現在サポートされているモデルが必要かどうか(アプリシェルの利点)によって異なります。
サービス ワーカーによるストリーミング レスポンスは、まだ初期段階です。さまざまなモデルが成熟し、特に一般的なユースケースを自動化するためのツールが開発されることを期待しています。
ストリームについて詳しく
独自の読み取り可能なストリームを構築する場合、controller.enqueue()
を無差別に呼び出すだけでは、十分でないか効率的でない可能性があります。Jake は、start()
、pull()
、cancel()
メソッドを組み合わせて使用し、ユースケースに合わせてデータ ストリームを作成する方法について詳しく説明しています。
詳細については、ストリーム仕様をご覧ください。
互換性
ReadableStream
をソースとして使用してサービス ワーカー内で Response
オブジェクトを作成するためのサポートが、Chrome 52 で追加されました。
Firefox のサービス ワーカーの実装では、ReadableStream
を基盤とするレスポンスはまだサポートされていませんが、Streams API のサポートに関する関連するトラッキング バグがあります。
Edge での接頭辞なしの Streams API サポートの進捗状況と、全体的な サービス ワーカーのサポートの進捗状況は、Microsoft のプラットフォーム ステータス ページで確認できます。