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