背景擷取功能簡介

Jake Archibald
Jake Archibald

我們在 2015 年推出 Background Sync,讓服務工作人員可以延後工作,直到使用者連上網路為止。也就是說,使用者可以輸入訊息、按下傳送,然後離開網站,因為系統會在連線時傳送訊息。

這項功能相當實用,但需要服務工作人員在擷取期間保持運作。如果是傳送訊息等短時間的工作,這不會造成問題,但如果工作耗時過長,瀏覽器就會終止服務工作人員,否則會對使用者的隱私權和電池造成風險。

如果需要下載電影、Podcast 或遊戲關卡等耗時內容,這就是背景擷取的用途。

自 Chrome 74 版起,Background Fetch 預設為可用狀態。

以下是兩分鐘的快速示範,說明傳統狀態與使用 Background Fetch 的差異:

運作方式

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

  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 採用三個引數:

參數
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
回應主體總大小 (解壓縮後)。

雖然這是選填欄位,但我們強烈建議您提供這項資訊。這個屬性用於告知使用者下載大小,並提供進度資訊。如果未提供這項資訊,瀏覽器會告知使用者大小不明,導致使用者較有可能取消下載。

如果背景擷取下載次數超過此處指定次數,系統就會中止下載。如果下載內容小於 downloadTotal,完全沒問題。因此,如果不確定下載總量,最好謹慎一點。

backgroundFetch.fetch 會傳回以 BackgroundFetchRegistration 解析的 Promise。稍後我會詳細說明。如果使用者選擇不下載,或提供的其中一個參數無效,這個 Promise 就會遭到拒絕。

為單一背景擷取作業提供多個要求,可讓您將邏輯上對使用者而言是單一項目的內容合併。舉例來說,一部電影可能會分割成數千個資源 (通常是 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" - 其中一個回應的狀態為「not-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 一或多項擷取作業失敗。
backgroundfetchabort 一或多項擷取作業失敗。

如果您想清除相關資料,這項功能就非常實用。

backgroundfetchclick 使用者點選下載進度 UI。

事件物件具有下列屬性:

屬性
registration BackgroundFetchRegistration
方法
updateUI({ title, icons }) 可變更您一開始設定的標題/圖示。這不是必要步驟,但您可以視需要提供更多背景資訊。您只能在 backgroundfetchsuccessbackgroundfetchfailure 活動期間「一次」執行這項操作。

針對成功/失敗做出回應

我們已經看過 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 錯誤,而這對您來說可能並不重要,因此仍值得將部分回應複製到快取中,如上所述。

回應點擊動作

顯示下載進度和結果的 UI 可供點選。服務工作人員中的 backgroundfetchclick 事件可讓您對此做出反應。如上所述,event.registration 將是背景擷取註冊。

這個事件的常見做法是開啟視窗:

addEventListener('backgroundfetchclick', (event) => {
  const bgFetch = event.registration;

  if (bgFetch.result === 'success') {
    clients.openWindow('/latest-podcasts');
  } else {
    clients.openWindow('/download-progress');
  }
});

其他資源

更正:本文先前版本誤稱 Background Fetch 為「網路標準」。這項 API 目前不在標準軌上,規格可在 WICG 中以「草案社群群組報告」的形式找到。