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

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 は拒否されます。

Service Worker イベント

イベント
backgroundfetchsuccess すべて正常に取得されました。
backgroundfetchfailure 1 つ以上の取得が失敗しました。
backgroundfetchabort 1 つ以上の取得が失敗しました。

これは、関連データをクリーンアップする場合にのみ有用です。

backgroundfetchclick ユーザーがダウンロード プログレス UI をクリックしました。

イベント オブジェクトには次のものがあります。

プロパティ
registration BackgroundFetchRegistration
メソッド
updateUI({ title, icons }) 最初に設定したタイトルやアイコンを変更できます。これは省略可能ですが、必要に応じて追加のコンテキストを提供できます。これは、backgroundfetchsuccess イベントと backgroundfetchfailure イベント中に *1 回* だけ実行できます。

成功/失敗への対応

progress イベントはすでに説明しましたが、これはユーザーがサイトのページを開いている間のみ有用です。バックグラウンド取得の主なメリットは、ユーザーがページを離れた、またはブラウザを閉じた後も、処理が継続されることです。

バックグラウンド取得が正常に完了すると、Service Worker は 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 のコミュニティ グループ レポートのドラフトとして確認できます。