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

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

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

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

Chrome 74 以降では、Background Fetch がデフォルトで利用可能です。

従来の状況とバックグラウンド フェッチの使用を比較する 2 分間の簡単なデモをご覧ください。

仕組み

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

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

ステップ 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 が既存のバックグラウンド フェッチと一致する場合、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 つのものを組み合わせることができます。たとえば、映画は数千のリソースに分割され(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
バックグラウンド フェッチが登録されたときに指定された値、または 0。
downloaded number
正常に受信したバイト数。

この値は減少する可能性があります。たとえば、接続が切断され、ダウンロードを再開できない場合などです。この場合、ブラウザはそのリソースのフェッチを最初から再開します。

result

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

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

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

  • "" - バックグラウンド取得が失敗していません。
  • "aborted" - バックグラウンド フェッチがユーザーによって中止されたか、abort() が呼び出されました。
  • "bad-status" - レスポンスのいずれかのステータスが OK 以外(404 など)でした。
  • "fetch-error" - 取得のいずれかが他の理由で失敗しました(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 はバックグラウンド フェッチ登録になります。

このイベントの後、フェッチされたリクエストとレスポンスにはアクセスできなくなるため、それらを保持したい場合は、キャッシュ 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 でドラフト コミュニティ グループ レポートとして確認できます。