背景擷取功能簡介

Jake Archibald
Jake Archibald

我們在 2015 年推出了背景同步處理功能,可讓服務工作站將工作延後,直到使用者連上網路為止。這表示使用者能夠輸入訊息、按下傳送並離開網站,知道訊息將立即傳送或有連線的時候。

這項功能相當實用,但需要服務工作者在擷取期間保持運作。對於傳送訊息這類短暫的工作來說,這並不是問題,但如果工作耗時過長,瀏覽器就會終止服務工作,否則可能會影響使用者的隱私權和電池。

那麼,如果需要下載的內容可能需要很長的時間,例如電影、Podcast 或遊戲關卡,該怎麼辦呢?這就是背景擷取的用途。

自 Chrome 74 起,背景擷取功能預設為可用。

下面是兩分鐘的簡短示範,說明比較傳統操作方式和背景擷取功能使用的狀態:

親自試用這個示範,並瀏覽程式碼

運作方式

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

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

參數
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 解析。我稍後會詳細說明。如果使用者已選擇不下載,或提供的其中一個參數無效,則承諾會遭到拒絕。

如要為單一背景擷取提供多個要求,您可以將邏輯上只有單一項目的內容結合給使用者。舉例來說,一部電影可能會分割成數千個資源 (通常是 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,就無法使用 matchmatchAll

方法
abort() 傳回 Promise<boolean>
終止背景擷取作業。

如果擷取作業已成功中止,則傳回的承諾會以 true 解析。

matchAll(request, opts) 傳回 Promise<Array<BackgroundFetchRecord>>
取得要求和回應。

這裡的引數與快取 API 相同。不帶引數的呼叫會傳回所有記錄的承諾。

詳情請見下面的說明。

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>
擷取的回應。

對方可能尚未收到回覆,但保證不會收到回覆。如果擷取失敗,承諾就會遭到拒絕。

Service worker 事件

活動
backgroundfetchsuccess 已成功擷取所有內容。
backgroundfetchfailure 發生一或多個擷取失敗。
backgroundfetchabort 一或多項擷取作業失敗。

只有在您想清理相關資料時,這項功能才會真正派上用場。

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

事件物件包含以下內容:

屬性
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 錯誤,而這可能對您而言不重要,因此還是值得將部分回應複製到快取中,如上所述。

回應點擊

顯示下載進度和結果的 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 中找到規格,也就是社群群組報告草稿。