2015 年に バックグラウンド同期を導入しました。これにより、サービス ワーカーはユーザーが接続を確立するまで作業を延期できます。つまり、ユーザーはメッセージを入力して送信ボタンを押すと、メッセージがすぐに送信されるか、接続が確立されたときに送信されることを知ったうえで、サイトを離れることができます。
便利な機能ですが、フェッチの期間中、サービス ワーカーがアクティブである必要があります。メッセージの送信のような短い作業では問題ありませんが、タスクに時間がかかりすぎると、ブラウザがサービス ワーカーを強制終了します。そうしないと、ユーザーのプライバシーとバッテリーにリスクが生じます。
映画、ポッドキャスト、ゲームのレベルなど、ダウンロードに時間がかかるものをダウンロードする必要がある場合はどうすればよいでしょうか?バックグラウンド フェッチは、そのためにあります。
Chrome 74 以降では、Background Fetch がデフォルトで利用可能です。
従来の状況とバックグラウンド フェッチの使用を比較する 2 分間の簡単なデモをご覧ください。
仕組み
バックグラウンド フェッチは次のように機能します。
- ブラウザに、一連のフェッチをバックグラウンドで実行するよう指示します。
- ブラウザはそれらを取得し、ユーザーに進行状況を表示します。
- フェッチが完了または失敗すると、ブラウザはサービス ワーカーを開き、イベントを発生させて結果を通知します。ここで、レスポンスをどのように処理するかを決定します。
ステップ 1 の後でユーザーがサイトのページを閉じても、ダウンロードは継続されるので問題ありません。フェッチは非常に目立ち、簡単に中止できるため、バックグラウンド同期タスクが長すぎるというプライバシー上の懸念はありません。サービス ワーカーは常に実行されているわけではないため、バックグラウンドでビットコインをマイニングするなど、システムを不正使用する心配はありません。
一部のプラットフォーム(Android など)では、ブラウザがオペレーティング システムにフェッチを渡すことができるため、ステップ 1 の後にブラウザが閉じる可能性があります。
ユーザーがオフライン中にダウンロードを開始した場合、またはダウンロード中にオフラインになった場合、バックグラウンド フェッチは一時停止され、後で再開されます。
API
機能検出
新しい機能と同様に、ブラウザがその機能をサポートしているかどうかを検出する必要があります。バックグラウンド フェッチの場合は、次のように簡単に記述できます。
if ('BackgroundFetchManager' in self) {
  // This browser supports Background Fetch!
}
バックグラウンド フェッチを開始する
メイン API はサービス ワーカーの登録に依存しているため、最初にサービス ワーカーを登録してください。以下の手順を行います。
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 で一般的)、画像などの追加リソースが付属している場合があります。ゲームのレベルは、多くの JavaScript、画像、音声リソースに分散している可能性があります。しかし、ユーザーにとっては単に「映画」や「レベル」です。
既存のバックグラウンド フェッチを取得する
既存のバックグラウンド フェッチは次のように取得できます。
navigator.serviceWorker.ready.then(async (swReg) => {
  const bgFetch = await swReg.backgroundFetch.get('my-fetch');
});
…必要なバックグラウンド フェッチの id を渡すことで、その ID のアクティブなバックグラウンド取得がない場合、get は 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バックグラウンド フェッチが登録されたときに指定された値、または 0。 | 
| downloaded | number正常に受信したバイト数。 この値は減少する可能性があります。たとえば、接続が切断され、ダウンロードを再開できない場合などです。この場合、ブラウザはそのリソースのフェッチを最初から再開します。 | 
| result | 次のいずれかになります。 
 | 
| failureReason | 次のいずれかになります。 
 | 
| recordsAvailable | boolean基盤となるリクエスト/レスポンスにアクセスできますか? これが false になると、 | 
| メソッド | |
| abort() | Promise<boolean>バックグラウンド フェッチを中止します。 取得が正常に中止された場合、返された Promise は true で解決されます。 | 
| matchAll(request, opts) | Promise<Array<BackgroundFetchRecord>>リクエストとレスポンスを取得します。 ここでの引数は、キャッシュ 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 | 
| メソッド | |
| updateUI({ title, icons }) | 最初に設定したタイトルやアイコンを変更できます。これは省略可能ですが、必要に応じてコンテキストを追加できます。これは backgroundfetchsuccessイベントとbackgroundfetchfailureイベントの間に *1 回だけ* 実行できます。 | 
成功/失敗への対応
progress イベントについてはすでに説明しましたが、これはユーザーがサイトのページを開いている間のみ有効です。バックグラウンド フェッチの主なメリットは、ユーザーがページを離れた後やブラウザを閉じた後でも処理が継続されることです。
バックグラウンド フェッチが正常に完了すると、サービス ワーカーは backgroundfetchsuccess イベントを受け取り、event.registration はバックグラウンド フェッチ登録になります。
このイベントの後、フェッチされたリクエストとレスポンスにはアクセスできなくなるため、それらを保持したい場合は、キャッシュ API などの場所に移動します。
ほとんどのサービス ワーカー イベントと同様に、event.waitUntil を使用して、イベントが完了したタイミングをサービス ワーカーに知らせます。
たとえば、サービス ワーカーでは次のようになります。
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 はクリック可能です。サービス ワーカーの 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 でドラフト コミュニティ グループ レポートとして確認できます。