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