填寫這份《一分鐘調查》,幫我們(開發組)做得更好!去填寫Home

與 Service Worker 通訊

Service worker communication

ServiceWorkerModule 匯入到你的 AppModule 中不僅會註冊 Service Worker,還會提供一些服務,讓你能和 Service Worker 通訊,並控制你的應用快取。

Importing ServiceWorkerModule into your AppModule doesn't just register the service worker, it also provides a few services you can use to interact with the service worker and control the caching of your app.

前提條件

Prerequisites

對下列知識有基本的瞭解:

A basic understanding of the following:


SwUpdate 服務

SwUpdate service

SwUpdate 服務讓你能訪問一些事件,這些事件會指出 Service Worker 何時發現了可用的更新或者一個更新何時可以被啟用 —— 這意味著它現在可以透過更新後的版本提供服務了。

The SwUpdate service gives you access to events that indicate when the service worker has discovered an available update for your app or when it has activated such an update—meaning it is now serving content from that update to your app.

SwUpdate 服務支援四個獨立的操作:

The SwUpdate service supports four separate operations:

  • 獲取出現可用更新的通知。如果要重新整理頁面,這些就是可載入的新版本。

    Getting notified of available updates. These are new versions of the app to be loaded if the page is refreshed.

  • 獲取更新被啟用的通知。這時候 Service Worker 就可以立即使用這些新版本提供服務了。

    Getting notified of update activation. This is when the service worker starts serving a new version of the app immediately.

  • 要求 Service Worker 向伺服器查詢是否有新版本。

    Asking the service worker to check the server for new updates.

  • 要求 Service Worker 為當前標籤頁啟用該應用的最新版本。

    Asking the service worker to activate the latest version of the app for the current tab.

有可用更新及已啟用更新

Available and activated updates

這兩個更新事件 availableactivated,都是 SwUpdateObservable 屬性:

The two update events, available and activated, are Observable properties of SwUpdate:

@Injectable() export class LogUpdateService { constructor(updates: SwUpdate) { updates.available.subscribe(event => { console.log('current version is', event.current); console.log('available version is', event.available); }); updates.activated.subscribe(event => { console.log('old version was', event.previous); console.log('new version is', event.current); }); } }
log-update.service.ts
      
      @Injectable()
export class LogUpdateService {

  constructor(updates: SwUpdate) {
    updates.available.subscribe(event => {
      console.log('current version is', event.current);
      console.log('available version is', event.available);
    });
    updates.activated.subscribe(event => {
      console.log('old version was', event.previous);
      console.log('new version is', event.current);
    });
  }
}
    

你可以使用這些事件來通知使用者有一個待做更新或當它們執行的程式碼已經過期時重新整理頁面。

You can use these events to notify the user of a pending update or to refresh their pages when the code they are running is out of date.

檢查更新

Checking for updates

可以要求 Service Worker 檢查是否有任何更新已經發布到了伺服器上。 Service Worker 會在初始化和每次導航請求(也就是使用者導航到應用中的另一個地址)時檢查更新。 不過,如果你的站點更新非常頻繁,或者需要按計劃進行更新,你可能會選擇手動檢查更新。

It's possible to ask the service worker to check if any updates have been deployed to the server. The service worker checks for updates during initialization and on each navigation request—that is, when the user navigates from a different address to your app. However, you might choose to manually check for updates if you have a site that changes frequently or want updates to happen on a schedule.

透過呼叫 checkForUpdate() 方法來實現:

Do this with the checkForUpdate() method:

import { ApplicationRef, Injectable } from '@angular/core'; import { SwUpdate } from '@angular/service-worker'; import { concat, interval } from 'rxjs'; import { first } from 'rxjs/operators'; @Injectable() export class CheckForUpdateService { constructor(appRef: ApplicationRef, updates: SwUpdate) { // Allow the app to stabilize first, before starting polling for updates with `interval()`. const appIsStable$ = appRef.isStable.pipe(first(isStable => isStable === true)); const everySixHours$ = interval(6 * 60 * 60 * 1000); const everySixHoursOnceAppIsStable$ = concat(appIsStable$, everySixHours$); everySixHoursOnceAppIsStable$.subscribe(() => updates.checkForUpdate()); } }
check-for-update.service.ts
      
      import { ApplicationRef, Injectable } from '@angular/core';
import { SwUpdate } from '@angular/service-worker';
import { concat, interval } from 'rxjs';
import { first } from 'rxjs/operators';

@Injectable()
export class CheckForUpdateService {

  constructor(appRef: ApplicationRef, updates: SwUpdate) {
    // Allow the app to stabilize first, before starting polling for updates with `interval()`.
    const appIsStable$ = appRef.isStable.pipe(first(isStable => isStable === true));
    const everySixHours$ = interval(6 * 60 * 60 * 1000);
    const everySixHoursOnceAppIsStable$ = concat(appIsStable$, everySixHours$);

    everySixHoursOnceAppIsStable$.subscribe(() => updates.checkForUpdate());
  }
}
    

該方法返回一個用來表示檢查更新已經成功完成的 Promise,不過它不會指出是否確實發現了一個更新。 即使找到了一個,Service Worker 還必須成功下載更新過的檔案,而這可能會失敗。如果成功了,就會透過一個 available 事件來表明當前應用有一個可用的新版本。

This method returns a Promise which indicates that the update check has completed successfully, though it does not indicate whether an update was discovered as a result of the check. Even if one is found, the service worker must still successfully download the changed files, which can fail. If successful, the available event will indicate availability of a new version of the app.

為了避免影響頁面的首次渲染,在註冊 ServiceWorker 指令碼之前,ServiceWorkerModule 預設會在應用程式達到穩定態之前等待最多 30 秒。如果不斷輪詢更新(比如呼叫 setInterval() 或 RxJS 的 interval())就會阻止應用程式達到穩定態,則直到 30 秒結束之前都不會往瀏覽器中註冊 ServiceWorker 指令碼。

In order to avoid negatively affecting the initial rendering of the page, ServiceWorkerModule waits for up to 30 seconds by default for the app to stabilize, before registering the ServiceWorker script. Constantly polling for updates, for example, with setInterval() or RxJS' interval(), will prevent the app from stabilizing and the ServiceWorker script will not be registered with the browser until the 30 seconds upper limit is reached.

請注意,應用中所執行的各種輪詢都會阻止它達到穩定態。欲知詳情,參閱 isStable 文件。

Note that this is true for any kind of polling done by your application. Check the isStable documentation for more information.

你可以透過在開始輪詢更新之前先等應用達到穩定態來消除這種延遲,如下面的例子所示。 另外,你還可以為 ServiceWorker 定義不一樣的 註冊策略

You can avoid that delay by waiting for the app to stabilize first, before starting to poll for updates, as shown in the example above. Alternatively, you might want to define a different registration strategy for the ServiceWorker.

強制啟用更新

Forcing update activation

如果當前標籤頁需要立即更新到最新的應用版本,可以透過 activateUpdate() 方法來要求立即這麼做:

If the current tab needs to be updated to the latest app version immediately, it can ask to do so with the activateUpdate() method:

@Injectable() export class PromptUpdateService { constructor(updates: SwUpdate) { updates.available.subscribe(event => { if (promptUser(event)) { updates.activateUpdate().then(() => document.location.reload()); } }); } }
prompt-update.service.ts
      
      @Injectable()
export class PromptUpdateService {

  constructor(updates: SwUpdate) {
    updates.available.subscribe(event => {
      if (promptUser(event)) {
        updates.activateUpdate().then(() => document.location.reload());
      }
    });
  }
}
    

如果呼叫 activateUpdate() 而不重新整理頁面,可能會破壞正在執行的應用中的延遲載入模組,特別是如果延遲載入的模組檔名中使用了雜湊時,就會改變每一個版本。 所以,建議每當 activateUpdate() 返回的 Promise 被解析時,都重新整理一次頁面。

Calling activateUpdate() without reloading the page could break lazy-loading in a currently running app, especially if the lazy-loaded chunks use filenames with hashes, which change every version. Therefore, it is recommended to reload the page once the promise returned by activateUpdate() is resolved.

處理不可恢復的狀態

Handling an unrecoverable state

在某些情況下,Service Worker 用來為客戶端提供服務的應用版本可能處於損壞狀態,如果不重新載入整個頁面,則無法恢復該狀態。

In some cases, the version of the app used by the service worker to serve a client might be in a broken state that cannot be recovered from without a full page reload.

例如,設想以下情形:

For example, imagine the following scenario:

  • 使用者首次開啟該應用,Service Worker 會快取該應用的最新版本。假設應用要快取的資源包括 index.htmlmain.<main-hash-1>.jslazy-chunk.<lazy-hash-1>.js

    A user opens the app for the first time and the service worker caches the latest version of the app. Let's assume the app's cached assets include index.html, main.<main-hash-1>.js and lazy-chunk.<lazy-hash-1>.js.

  • 使用者關閉該應用程式,並且有一段時間沒有開啟它。

    The user closes the app and does not open it for a while.

  • 一段時間後,會將新版本的應用程式部署到伺服器。新版本中包含檔案 index.htmlmain.<main-hash-2>.jslazy-chunk.<lazy-hash-2>.js (請注意,雜湊值現在已經不同了,因為檔案的內容已經改變)。伺服器上不再提供舊版本。

    After some time, a new version of the app is deployed to the server. This newer version includes the files index.html, main.<main-hash-2>.js and lazy-chunk.<lazy-hash-2>.js (note that the hashes are different now, because the content of the files has changed). The old version is no longer available on the server.

  • 同時,使用者的瀏覽器決定從其快取中清退 lazy-chunk.<lazy-hash-1>.js 瀏覽器可能決定從快取中清退特定(或所有)資源,以便回收磁碟空間。

    In the meantime, the user's browser decides to evict lazy-chunk.<lazy-hash-1>.js from its cache. Browsers may decide to evict specific (or all) resources from a cache in order to reclaim disk space.

  • 使用者再次開啟本應用。此時,Service Worker 將提供它所知的最新版本,當然,實際上對我們是舊版本( index.htmlmain.<main-hash-1>.js )。

    The user opens the app again. The service worker serves the latest version known to it at this point, namely the old version (index.html and main.<main-hash-1>.js).

  • 在稍後的某個時刻,該應用程式請求惰性捆綁套件 lazy-chunk.<lazy-hash-1>.js

    At some later point, the app requests the lazy bundle, lazy-chunk.<lazy-hash-1>.js.

  • Service Worker 無法在快取中找到該資產(請記住瀏覽器已經將其清退了)。它也無法從伺服器上獲取它(因為伺服器現在只有較新版本的 lazy-chunk.<lazy-hash-2>.js

    The service worker is unable to find the asset in the cache (remember that the browser evicted it). Nor is it able to retrieve it from the server (since the server now only has lazy-chunk.<lazy-hash-2>.js from the newer version).

在上述情況下,Service Worker 將無法提供通常會被快取的資產。該特定的應用程式版本已損壞,並且無法在不重新載入頁面的情況下修復客戶端的狀態。在這種情況下,Service Worker 會透過傳送 UnrecoverableStateEvent 事件來通知客戶端。你可以訂閱 SwUpdate#unrecoverable 以得到通知並處理這些錯誤。

In the above scenario, the service worker is not able to serve an asset that would normally be cached. That particular app version is broken and there is no way to fix the state of the client without reloading the page. In such cases, the service worker notifies the client by sending an UnrecoverableStateEvent event. You can subscribe to SwUpdate#unrecoverable to be notified and handle these errors.

@Injectable() export class HandleUnrecoverableStateService { constructor(updates: SwUpdate) { updates.unrecoverable.subscribe(event => { notifyUser( `An error occurred that we cannot recover from:\n${event.reason}\n\n` + 'Please reload the page.'); }); } }
handle-unrecoverable-state.service.ts
      
      @Injectable()
export class HandleUnrecoverableStateService {
  constructor(updates: SwUpdate) {
    updates.unrecoverable.subscribe(event => {
      notifyUser(
        `An error occurred that we cannot recover from:\n${event.reason}\n\n` +
        'Please reload the page.');
    });
  }
}
    

關於 Angular Service Worker 的更多資訊

More on Angular service workers

你可能還對下列內容感興趣:

You may also be interested in the following: