背景擷取功能簡介

阿奇巴德 (Jake Archibald)
Jake Archibald

我們在 2015 年推出背景同步處理,可讓服務工作站延遲工作,直到使用者連上網路為止。這表示使用者可以輸入訊息、按下傳送按鈕,並讓網站知道訊息會在現在或能夠連線時傳送。

這是一項實用功能,但它需要服務工作站在擷取期間保持運作。以傳送訊息等小作業來說,這並非問題,但如果工作執行時間過長,瀏覽器就會終止服務工作站,否則對使用者的隱私和電池有風險。

因此,如果需要下載可能需要較長時間的內容,例如電影、Podcast 或遊戲的關卡,該怎麼做呢?這就是「背景擷取」的功能。

自 Chrome 74 版起,系統會預設使用背景擷取功能。

以下提供 2 分鐘的簡短示範,比較傳統的內容和背景擷取功能:

親自試用示範模式瀏覽程式碼

運作方式

背景擷取的運作方式如下:

  1. 您可以指示瀏覽器在背景執行一組擷取作業。
  2. 瀏覽器會擷取這些內容,並向使用者顯示進度。
  3. 擷取完成或失敗後,瀏覽器會開啟 Service Worker 並觸發事件,讓您瞭解情況。您可以在這裡決定回應 (如有) 的處理方式。

如果使用者在步驟 1 後關閉了網站頁面,沒有問題,下載作業會繼續執行。由於擷取作業的瀏覽權限相當高,而且很容易取消,因此採用長效做法的背景同步處理工作並不構成隱私權方面的疑慮。由於 Service Worker 不是持續執行,因此您不必擔心可能會濫用系統,例如在背景中採礦比特幣。

在部分平台 (例如 Android) 中,瀏覽器可在步驟 1 之後關閉,因為瀏覽器可以將擷取作業交給作業系統。

如果使用者在離線時開始下載,或在下載期間離線,背景擷取就會暫停,並於稍後繼續。

API

特徵偵測

與任何新功能一樣,建議您偵測瀏覽器是否支援此功能。背景擷取作業非常簡單:

if ('BackgroundFetchManager' in self) {
  // This browser supports Background Fetch!
}

開始背景擷取

主要 API 會停止服務工作站註冊,因此請確認您已註冊 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 會使用三個引數:

參數
id string
專門用於識別這項背景擷取作業。

如果 ID 與現有的背景擷取相符,backgroundFetch.fetch 將會拒絕。

requests Array<Request|string>
要擷取的項目。系統會將字串視為網址,並透過 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 解析。稍後會再詳細說明如果使用者已選擇停止下載,或提供的其中一個參數無效,Remise 就會拒絕。

如果為單一背景擷取提供多個要求,您就可以將根據邏輯,為使用者合併的單一項目。例如,電影可分割成 1000 年代的資源 (與 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
註冊背景擷取時提供的值,或是零。
downloaded number
成功收到的位元組數。

這個值可能會減少。舉例來說,如果連線中斷且無法繼續下載,瀏覽器就會從頭開始重新擷取該資源。

result

可以是下列其中一項:

  • "" - 背景擷取已啟用,因此尚無結果。
  • "success" - 背景擷取成功。
  • "failure" - 背景擷取失敗。只有在背景擷取完全失敗時,才會顯示這個值,因為瀏覽器無法重試/繼續。
failureReason

可以是下列其中一項:

  • "":未失敗背景擷取。
  • "aborted":使用者已取消背景擷取,或呼叫 abort()
  • "bad-status":其中一個回應的狀態無效,例如 404。
  • "fetch-error" - 其中一個擷取作業因其他原因而失敗,例如 CORS、MIX、部分回應無效,或是無法重試的擷取作業一般網路失敗。
  • "quota-exceeded" - 在背景擷取期間達到儲存空間配額。
  • "download-total-exceeded" - 超過提供的「downloadTotal」。
recordsAvailable boolean
可以存取基礎要求/回應嗎?

一旦這個值為 false,match 就無法使用 matchAll

方法
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 一或多個擷取作業失敗。
backgroundfetchabort 一或多項擷取作業失敗。

只有在您想要清理相關資料時,才能使用這項功能。

backgroundfetchclick 使用者點選下載進度使用者介面。

事件物件包含下列項目:

屬性
registration BackgroundFetchRegistration
方法
updateUI({ title, icons }) 可讓您變更一開始設定的標題/圖示。這是選填欄位,但可讓您視需要提供更多背景資訊。在 backgroundfetchsuccessbackgroundfetchfailure 事件期間,您只能 *執行一次* 這項操作。

回應成功/失敗

已看過 progress 事件,但這只有在使用者開啟網站頁面時才實用。背景擷取的主要優點是,在使用者離開頁面,甚至是關閉瀏覽器後仍會繼續運作。

如果背景擷取成功完成,服務工作站就會收到 backgroundfetchsuccess 事件,而 event.registration 會是背景擷取註冊作業。

在此事件過後,您就無法再存取擷取的要求和回應,因此如要保留這些要求,請將這些內容移至快取 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 可能對您來說並不重要,因此仍值得將一些回應複製到快取中,如上所述。

回應點擊

顯示下載進度和結果的使用者介面可點選。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 中找到「草擬社群群組報告」中的規格