2015 年に、Service Worker はユーザーが接続されるまで作業を遅らせることができるバックグラウンド同期が導入されました。つまり、ユーザーはメッセージを入力して [送信] を押すと、メッセージをすぐにまたは接続したときにメッセージが送信されることを認識して、サイトを離れる可能性があります。
これは便利な機能ですが、取得中に Service Worker が動作している必要があります。メッセージの送信などの短い処理では問題になりませんが、タスクに時間がかかりすぎると、ブラウザが Service Worker を強制終了します。そうしないと、ユーザーのプライバシーとバッテリーが危険にさらされます。
映画、ポッドキャスト、ゲームのレベルなど、時間がかかるものをダウンロードする必要がある場合はどうすればよいでしょうか。これがバックグラウンド取得の役割です。
バックグラウンド取得は、Chrome 74 以降、デフォルトで使用できます。
以下の 2 分間の簡単なデモでは、バックグラウンド フェッチを使用した場合と、従来の状態の違いを説明します。
ご自身でデモを試して、コードを確認してください。
仕組み
バックグラウンド フェッチの仕組みは次のとおりです。
- 複数のフェッチをバックグラウンドで実行するようにブラウザに指示します。
- ブラウザはこれらを取得して進行状況をユーザーに表示します。
- 取得が完了または失敗すると、ブラウザが Service Worker を開き、何が起こったかを知らせるイベントを発生させます。ここで、レスポンスの処理方法を決定します。
ステップ 1 の後にユーザーがサイトのページを閉じても、ダウンロードは続行されます。フェッチは見やすく、簡単に中止できるため、バックグラウンド同期タスクが長すぎるというプライバシーに関する懸念はありません。Service Worker は常に実行されているわけではないため、バックグラウンドでビットコイン マイニングを行うなど、システムを悪用するおそれはありません。
一部のプラットフォーム(Android など)では、ブラウザがオペレーティング システムにフェッチを渡せるため、ステップ 1 の後にブラウザを閉じることができます。
ユーザーがオフライン中にダウンロードを開始した場合、またはダウンロード中にオフラインになった場合、バックグラウンドの取得は一時停止され、後で再開されます。
API
特徴検出
他の新機能と同様に、ブラウザがそれをサポートしているかどうかを確かめる必要があります。バックグラウンド フェッチの場合、次のように簡単です。
if ('BackgroundFetchManager' in self) {
// This browser supports Background Fetch!
}
バックグラウンド取得の開始
メイン API は Service Worker の登録からハングするため、先に Service Worker を登録してください。以下の手順を行います。
navigator.serviceWorker.ready.then(async (swReg) => {
const bgFetch = await swReg.backgroundFetch.fetch('my-fetch', ['/ep-5.mp3', 'ep-5-artwork.jpg'], {
title: 'Episode 5: Interesting things.',
icons: [{
sizes: '300x300',
src: '/ep-5-icon.png',
type: 'image/png',
}],
downloadTotal: 60 * 1024 * 1024,
});
});
backgroundFetch.fetch
は、次の 3 つの引数を取ります。
パラメータ | |
---|---|
id |
string このバックグラウンド取得を一意に識別します。 ID が既存のバックグラウンド フェッチと一致する場合、 |
requests |
Array<Request|string>
取得対象。文字列は URL として扱われ、 new Request(theString) によって Request に変換されます。
リソースが CORS を介して許可している限り、他のオリジンから物事を取得できます。 注: 現在、Chrome は CORS プリフライトを必要とするリクエストをサポートしていません。 |
options |
以下を含むオブジェクト。 |
options.title |
string 進行状況とともに表示されるブラウザのタイトル。 |
options.icons |
Array<IconDefinition> src、size、type を持つオブジェクトの配列。 |
options.downloadTotal |
number レスポンス本文の合計サイズ(gzip 圧縮解除後)。 この情報は省略可能ですが、指定することを強くおすすめします。サイズと進行状況をユーザーに伝えます。指定しない場合、ブラウザはユーザーにサイズが不明であると通知するため、ユーザーがダウンロードを中止する可能性が高くなります。 バックグラウンドでの取得がここで指定した数を超えると、中止されます。ダウンロードのサイズが |
backgroundFetch.fetch
は、BackgroundFetchRegistration
で解決される Promise を返します。これについては後で説明します。ユーザーがダウンロードを無効にしている場合、または指定されたパラメータのいずれかが無効な場合、Promise は拒否されます。
1 回のバックグラウンド取得に対して多数のリクエストを提供することで、ユーザーにとって論理的に 1 つのものを組み合わせることができます。たとえば、映画は数千ものリソース(MPEG-DASH で一般的)に分割され、画像などの追加リソースが含まれる場合があります。ゲームの 1 つのレベルが、多数の JavaScript、画像、音声リソースに分散される場合があります。しかしユーザーにとっては、それは単に「映画」または「レベル」です。
既存のバックグラウンド取得を取得する
既存のバックグラウンド取得は次のように取得できます。
navigator.serviceWorker.ready.then(async (swReg) => {
const bgFetch = await swReg.backgroundFetch.get('my-fetch');
});
...必要なバックグラウンド フェッチの id を渡すことで、get
は、その ID のアクティブなバックグラウンド フェッチがない場合、undefined
を返します。
バックグラウンド フェッチは、登録されてから、成功、失敗、または中止されるまで「アクティブ」とみなされます。
getIds
を使用すると、アクティブなバックグラウンド取得のリストを取得できます。
navigator.serviceWorker.ready.then(async (swReg) => {
const ids = await swReg.backgroundFetch.getIds();
});
バックグラウンド フェッチの登録
BackgroundFetchRegistration
(上記の例では bgFetch
)は次のとおりです。
プロパティ | |
---|---|
id |
string バックグラウンド取得の ID。 |
uploadTotal |
number サーバーに送信されるバイト数。 |
uploaded |
number 正常に送信されたバイト数。 |
downloadTotal |
number バックグラウンド フェッチの登録時に提供された値、またはゼロ。 |
downloaded |
number 正常に受信したバイト数。 この値は減少する可能性があります。たとえば、接続が切断され、ダウンロードを再開できない場合、ブラウザはそのリソースの取得をゼロから再開します。 |
result |
次のいずれかになります。
|
failureReason |
次のいずれかになります。
|
recordsAvailable |
boolean 基盤となるリクエスト/レスポンスにアクセスできますか? false に設定すると、 |
Methods | |
abort() |
Promise<boolean> を返します。バックグラウンド取得を中止します。 フェッチが正常に中止された場合、返された Promise は true で解決されます。 |
matchAll(request, opts) |
戻り値 Promise<Array<BackgroundFetchRecord>> 。リクエストとレスポンスを取得します。 ここでの引数は、Cache API と同じです。引数なしで呼び出すと、すべてのレコードの Promise が返されます。 詳細は以下のとおりです。 |
match(request, opts) |
上記と同様に Promise<BackgroundFetchRecord> を返しますが、最初の一致で解決されます。 |
イベント | |
progress |
uploaded 、downloaded 、result 、failureReason のいずれかが変更されたときに呼び出されます。 |
進捗状況の追跡
これを行うには、progress
イベントを使用します。downloadTotal
は指定した値になり、値を指定しない場合は 0
になります。
bgFetch.addEventListener('progress', () => {
// If we didn't provide a total, we can't provide a %.
if (!bgFetch.downloadTotal) return;
const percent = Math.round(bgFetch.downloaded / bgFetch.downloadTotal * 100);
console.log(`Download progress: ${percent}%`);
});
リクエストとレスポンスの取得
bgFetch.match('/ep-5.mp3').then(async (record) => {
if (!record) {
console.log('No record found');
return;
}
console.log(`Here's the request`, record.request);
const response = await record.responseReady;
console.log(`And here's the response`, response);
});
record
は BackgroundFetchRecord
で、次のようになります。
プロパティ | |
---|---|
request |
Request 提供されたリクエスト。 |
responseReady |
Promise<Response> 取得したレスポンス。 レスポンスがまだ受け取っていない可能性があるため、Promise の背後になっています。フェッチが失敗すると、Promise は拒否されます。 |
Service Worker イベント
イベント | |
---|---|
backgroundfetchsuccess |
すべてが正常に取得されました。 |
backgroundfetchfailure |
1 つ以上の取得が失敗しました。 |
backgroundfetchabort |
1 つ以上の取得が失敗しました。 これは、関連データのクリーンアップを行う場合にのみ有用です。 |
backgroundfetchclick |
ユーザーがダウンロード進行状況 UI をクリックした。 |
イベント オブジェクトには次のものがあります。
プロパティ | |
---|---|
registration |
BackgroundFetchRegistration |
Methods | |
updateUI({ title, icons }) |
初期設定しているタイトルまたはアイコンを変更できます。これは省略可能ですが、必要に応じて詳細なコンテキストを指定できます。これは、backgroundfetchsuccess イベントと backgroundfetchfailure イベント中に *1 回だけ* 実行できます。 |
成功/失敗への対応
progress
イベントはすでに確認しましたが、これが役立つのは、ユーザーがサイトのページを開いている間だけです。バックグラウンド取得の主なメリットは、ユーザーがページから離れた後、またはブラウザを閉じた後も処理が継続されることです。
バックグラウンド取得が正常に完了すると、Service Worker は backgroundfetchsuccess
イベントを受信し、event.registration
はバックグラウンド取得の登録です。
このイベントの後、フェッチ済みのリクエストとレスポンスにはアクセスできなくなるため、保持する場合は、キャッシュ API などに移動します。
ほとんどの Service Worker イベントと同様に、event.waitUntil
を使用して、イベントの完了を Service Worker が認識できるようにします。
たとえば、Service Worker では次のようになります。
addEventListener('backgroundfetchsuccess', (event) => {
const bgFetch = event.registration;
event.waitUntil(async function() {
// Create/open a cache.
const cache = await caches.open('downloads');
// Get all the records.
const records = await bgFetch.matchAll();
// Copy each request/response across.
const promises = records.map(async (record) => {
const response = await record.responseReady;
await cache.put(record.request, response);
});
// Wait for the copying to complete.
await Promise.all(promises);
// Update the progress notification.
event.updateUI({ title: 'Episode 5 ready to listen!' });
}());
});
失敗の原因は、1 つの 404 にすぎないかもしれないため、それほど重要ではなかったかもしれません。そのため、上記のように一部のレスポンスをキャッシュに保存することには意義があります。
クリックへの反応
ダウンロードの進行状況と結果を表示する UI がクリック可能になっている。Service Worker の backgroundfetchclick
イベントを使用すると、これに対応できます。上記のように、event.registration
はバックグラウンド取得の登録です。
このイベントでよく行うのは、ウィンドウを開くことです。
addEventListener('backgroundfetchclick', (event) => {
const bgFetch = event.registration;
if (bgFetch.result === 'success') {
clients.openWindow('/latest-podcasts');
} else {
clients.openWindow('/download-progress');
}
});
補足資料
修正: この記事の以前のバージョンでは、バックグラウンド取得が「ウェブ標準」と誤って記載されていました。この API は現在標準版に移行していません。この仕様は WICG のドラフト コミュニティ グループ レポートで確認できます。