バックグラウンド取得の概要

2015 年に、バックグラウンド同期を導入しました。これにより、サービス ワーカーはユーザーが接続するまで処理を延期できます。つまり、ユーザーはメッセージを入力して送信ボタンを押した後、メッセージがすぐに送信されるか、接続が確立されたときに送信されることを理解したうえでサイトを離れることができます。

これは便利な機能ですが、フェッチ中は Service Worker が存続している必要があります。これは、メッセージの送信などの短い処理では問題ありませんが、タスクに時間がかかりすぎると、ブラウザはサービス ワーカーを強制終了します。そうしないと、ユーザーのプライバシーとバッテリーにリスクが生じます。

映画、ポッドキャスト、ゲームのレベルなど、ダウンロードに時間がかかるコンテンツをダウンロードする必要がある場合はどうすればよいですか?そのために、バックグラウンド フェッチが用意されています。

バックグラウンド フェッチは、Chrome 74 以降ではデフォルトで使用できます。

バックグラウンド フェッチを使用する場合と使用しない場合の状態を示す 2 分間のデモをご覧ください。

デモを試すコードを参照する

仕組み

バックグラウンド フェッチは次のように機能します。

  1. ブラウザに、一連の取得をバックグラウンドで実行するよう指示します。
  2. ブラウザはこれらのファイルを取得し、進捗状況をユーザーに表示します。
  3. 取得が完了または失敗すると、ブラウザはサービス ワーカーを開き、イベントを発生させて結果を通知します。ここで、レスポンスをどのように処理するかを決定します。

ユーザーがステップ 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 が既存のバックグラウンド取得と一致する場合、backgroundFetch.fetch は拒否されます。

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 解凍後)。

これは省略可能ですが、入力することを強くおすすめします。ダウンロードのサイズをユーザーに伝え、進行状況情報を提供するのに使用されます。サイズを指定しないと、ブラウザはサイズが不明であることをユーザーに通知します。その結果、ユーザーはダウンロードを中止する可能性が高くなります。

バックグラウンド フェッチのダウンロード数がここに指定された数を超えると、中断されます。ダウンロードが downloadTotal より小さい場合は問題ありません。ダウンロードの合計が不明な場合は、念のため大きい値を指定することをおすすめします。

backgroundFetch.fetch は、BackgroundFetchRegistration で解決される Promise を返します。詳細については後で説明します。ユーザーがダウンロードをオプトアウトしている場合、または指定されたパラメータのいずれかが無効な場合、Promise は拒否されます。

1 つのバックグラウンド フェッチに対して複数のリクエストを指定すると、ユーザーにとって論理的に 1 つのリクエストとして扱われるものを組み合わせることができます。たとえば、映画は 1,000 個のリソースに分割され(MPEG-DASH で一般的)、画像などの追加リソースが付属している場合があります。ゲームのレベルは、多くの JavaScript、画像、音声リソースに分散できます。ただし、ユーザーにとっては「映画」や「レベル」にすぎません。

既存のバックグラウンド フェッチの取得

既存のバックグラウンド フェッチは次のように取得できます。

navigator.serviceWorker.ready.then(async (swReg) => {
  const bgFetch = await swReg.backgroundFetch.get('my-fetch');
});

必要なバックグラウンド フェッチの ID を渡します。その ID でアクティブなバックグラウンド フェッチがない場合、getundefined を返します。

バックグラウンド取得は、登録された瞬間から、成功、失敗、または中止されるまで「アクティブ」と見なされます。

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

次のいずれかになります。

  • "" - バックグラウンド フェッチがアクティブであるため、まだ結果はありません。
  • "success" - バックグラウンド取得が成功しました。
  • "failure" - バックグラウンド取得に失敗しました。この値は、ブラウザで再試行または再開できないなど、バックグラウンド フェッチが完全に失敗した場合にのみ表示されます。
failureReason

次のいずれかになります。

  • "" - バックグラウンド取得は失敗していません。
  • "aborted" - バックグラウンド フェッチがユーザーによって中断されたか、abort() が呼び出されました。
  • "bad-status" - レスポンスの 1 つが正常でないステータス(404 など)だった。
  • "fetch-error" - なんらかの理由で取得の 1 つが失敗しました(CORS、MIX、無効な部分的なレスポンス、再試行できない取得の一般的なネットワーク障害など)。
  • "quota-exceeded" - バックグラウンド取得中にストレージの割り当てに達しました。
  • "download-total-exceeded" - 指定された「downloadTotal」を超えました。
recordsAvailable boolean
基盤となるリクエスト/レスポンスにアクセスできますか?

これが false になると、matchmatchAll は使用できなくなります。

メソッド
abort() Promise<boolean> を返します。
バックグラウンド取得を中止します。

取得が正常に中止された場合、返された Promise は true で解決します。

matchAll(request, opts) Promise<Array<BackgroundFetchRecord>> を返します。
リクエストとレスポンスを取得します。

ここでの引数は、キャッシュ API と同じです。引数なしで呼び出すと、すべてのレコードの Promise が返されます。

詳しくは以下をご覧ください。

match(request, opts) Promise<BackgroundFetchRecord> を返します。
上記と同じですが、最初の一致で解決します。
イベント
progress uploadeddownloadedresultfailureReason のいずれかが変更されたときにトリガーされます。

進捗状況の追跡

これは 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);
});

recordBackgroundFetchRecord で、次のように表示されます。

プロパティ
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 はバックグラウンド フェッチの登録になります。

このイベントの発生後、フェッチされたリクエストとレスポンスにはアクセスできなくなります。保持する場合は、cache API などの場所に移動してください。

ほとんどの Service Worker イベントと同様に、event.waitUntil を使用して、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!' });
  }());
});

失敗は 404 が 1 つだけだった可能性があり、これは重要ではなかった可能性があります。そのため、上記のように一部のレスポンスをキャッシュにコピーする価値がある場合があります。

クリックへの対応

ダウンロードの進行状況と結果を示す 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 のコミュニティ グループ レポートのドラフトとして確認できます。