[譯]前端離線指南(上)

原文連接: The offline cookbook
做者:Jake Archibald

使用AppCache能夠爲咱們提供幾種支持內容離線工做的模式。若是這些模式正是你所須要的,那麼恭喜你,你中了APPCache的大獎(儘管頭等獎依然無人認領),但咱們這些其他的人都擠在角落裏來回搖擺(譯者注:做者指的是因爲設計上的緣由,AppCache逐漸地被Web標準移除,雖然如今依然有瀏覽器支持這個功能,但最好不要再使用它了)css

對於ServiceWorker(介紹),咱們放棄嘗試去解決離線問題,而且給開發者們提供靈活的組件從而讓他們本身去解決離線問題。它爲您提供了控制緩存和處理請求的方式。這就意味着您能夠建立您本身的模式。接下來讓咱們來看一下幾個隔離環境下的可行模式,可是在實踐中,您可能會根據URL和context以串聯方式用到其中的多個模式。html

目前,除非另有說明,全部的示例代碼均可以運行在Chrome和Firefox瀏覽器中。關於ServiceWorker支持程度的完整詳情,請查閱"Is Service Worker Ready?"html5

有關對於其中部分模式的運行演示,請查閱Trained-to-thrill,而且此處的視頻將向您展現性能影響。git

緩存機-什麼時候開始存儲資源?

您能夠經過ServiceWorker來獨立地從緩存中處理請求,因此咱們要先單獨地研究一下它們。首先,咱們啥時候應該進行緩存呢?github

安裝時——以依賴的形式

圖片描述

ServiceWorker提供給您一個install事件,您可使用它把資源準備好,即在處理其餘事件以前必需要提早準備好的東西。可是當這些操做正在進行中的時候,任何舊版本的ServiceWorker仍舊在運行而且提供給頁面,所以您在此處進行的操做必定不能中斷它們。json

適用於: CSS、圖片、字體、JS文件、模板等,基本包含了你認爲網站在當前「版本」中應該須要的全部靜態資源。promise

若是未能獲取上述資源,那麼您的網站徹底沒法運行,對應的本機應用會將這些對象包含在初始下載中。瀏覽器

self.addEventListener('install', function(event) {
  event.waitUntil(
    caches.open('mysite-static-v3').then(function(cache) {
      return cache.addAll([
        '/css/whatever-v3.css',
        '/css/imgs/sprites-v6.png',
        '/css/fonts/whatever-v8.woff',
        '/js/all-min-v4.js'
        // etc
      ]);
    })
  );
});

event.waitUntil接受一個promise對象做參數,來定義安裝時長和安裝是否成功,若是promise狀態爲rejected,則認爲這次安裝失敗,而且拋棄ServiceWorker(若是一箇舊版本的ServiceWorker正在運行,則它將保持不變)。caches.opencaches.addAll都返回promise對象,若是其中有任何一個資源獲取失敗,則caches.addAll會調用reject。
trained-to-thrill 上,我使用此方法緩存靜態資源緩存

安裝時——不做爲依賴

圖片描述

此方式與上述類似,但區別是:即便緩存失敗,既不會延遲安裝也不會致使安裝失敗。網絡

適用於: 體積較大的,且暫時用不到的資源,好比用於遊戲的較高級別的資源。

self.addEventListener('install', function(event) {
  event.waitUntil(
    caches.open('mygame-core-v1').then(function(cache) {
      cache.addAll(
        // levels 11-20
      );
      return cache.addAll(
        // core assets & levels 1-10
      );
    })
  );
});

咱們沒有將levels 11-20的cache.addAll promise對象,返回給event.waitUntil,因此事件即便失敗,遊戲在離線的時候依然可使用。固然,您必須考慮到缺乏這些level的狀況,若是缺乏它們,則嘗試從新緩存它們。

在當level 11-20正在下載的時候,ServiceWorker可能會終止,由於它已經完成處理事件。這就意味着它們就不會被緩存下來。將來,咱們計劃添加一個在後臺下載的API以處理相似這樣的狀況,以及下載像電影同樣的大致積文件。

激活時

適用於: 清理和遷移

圖片描述

在新的ServiceWorker已經被安裝,而且較早版本的sw沒有在使用的狀況下,則新的ServiceWorker會被激活,您就會獲得一個activate事件。因爲舊版本的退出,因此此時很是適合處理 IndexedDB 中的架構遷移和刪除未使用的緩存。

self.addEventListener('activate', function(event) {
  event.waitUntil(
    caches.keys().then(function(cacheNames) {
      return Promise.all(
        cacheNames.filter(function(cacheName) {
          // 若是您想刪除緩存,則返回true,
          // 可是請記住緩存在該域名內的全部頁面之間
          // 是共享的
        }).map(function(cacheName) {
          return caches.delete(cacheName);
        })
      );
    })
  );
});

在激活的過程當中,諸如fetch等事件會被放置在一個隊列中,因此一個長時間的激活可能會阻塞頁面加載。保證您的激活儘量地簡潔,僅用於舊版本處於活動狀態時沒法執行的操做。

trained-to-thrill上,我使用此方法移除舊緩存

在用戶交互時

圖片描述

適用於: 若是整個站點沒法離線工做,您能夠容許用戶選擇須要離線的可用內容,好比,YouTube上的某個視頻,維基百科上的某篇文章,Flickr上的某張圖片等等。

爲用戶提供一個「稍後閱讀」或者「離線保存」的按鈕。當點擊按鈕,從網絡中獲取您所須要的內容並把它放進緩存中。

document.querySelector('.cache-article').addEventListener('click', function(event) {
  event.preventDefault();

  var id = this.dataset.articleId;
  caches.open('mysite-article-' + id).then(function(cache) {
    fetch('/get-article-urls?id=' + id).then(function(response) {
      // /get-article-urls returns a JSON-encoded array of
      // resource URLs that a given article depends on
      return response.json();
    }).then(function(urls) {
      cache.addAll(urls);
    });
  });
});

cacheAPI在頁面既能夠在ServiceWorker中獲取到,也能夠在頁面中獲取到,這就意味着你沒必要必定要經過ServiceWorker來向緩存中添加內容。

網絡響應時

圖片描述

適用於: 頻繁更新的資源,好比用戶收件箱,或者文章內容。一樣適用於不重要但須要謹慎處理的內容,好比用戶頭像。

若是請求的資源與緩存中的任何資源均不匹配,則從網絡中獲取,將其發送到頁面中,同時將其添加到緩存中。

若是您針對一系列網址執行此操做,如頭像,那麼您須要謹慎,不要使域名下的存儲變得臃腫,若是用戶須要回收磁盤空間,您不會想成爲主要候選對象。請確保將緩存中再也不須要的項目刪除。

self.addEventListener('fetch', function(event) {
  event.respondWith(
    caches.open('mysite-dynamic').then(function(cache) {
      return cache.match(event.request).then(function (response) {
        return response || fetch(event.request).then(function(response) {
          cache.put(event.request, response.clone());
          return response;
        });
      });
    })
  );
});

爲了高效使用內存,只容許讀取一次responserequestbody,在上面的代碼中,使用.clone來建立可以單獨地讀取數據的額外副本。

trained-to-thrill上,我使用此方法緩存Flickr圖像

Stale-while-revalidate

圖片描述

適用於: 頻繁更新,但卻不必獲取最新的資源。用戶頭像就屬於此類。

若是緩存中已經有一個可用的版本,直接使用該版本,可是會爲了下一次的請求而獲取一個更新版本。

self.addEventListener('fetch', function(event) {
  event.respondWith(
    caches.open('mysite-dynamic').then(function(cache) {
      return cache.match(event.request).then(function(response) {
        var fetchPromise = fetch(event.request).then(function(networkResponse) {
          cache.put(event.request, networkResponse.clone());
          return networkResponse;
        })
        return response || fetchPromise;
      })
    })
  );
});

它和 HTTP 的 stale-while-revalidate 很是類似。

推送消息時

圖片描述

注意: Chrome暫時還不支持Push。(譯者注:Chrome 50及以後的版本開始支持,更多信息請參考 can i use

Push API是基於ServiceWorker構建的另外一個功能。它容許喚醒ServiceWorker以響應來自系統服務的消息,即便用戶沒有爲您的站點打開一個標籤,Push API也一樣能夠工做。只有ServiceWorker被喚醒。您從頁面請求執行此操做權限的同時,用戶也將收到提示。

適用於: 與通知有關的內容,好比聊天消息,突發新聞,或者Email等。一樣適用於不常常更改的可當即同步的內容,例如待辦事項更新或者日程表的更改。

用戶常見的頁面表現是,出現一個通知,當點擊它的時候,會打開或者聚焦到相關的頁面,可是在點擊它以前,務必要更新緩存。顯然,用戶在收到推送消息的時候,必定是在線的,可是,當他們最終與通知交互時可能已經離線,所以,容許離線訪問此內容很是重要。Twitter原生應用在大多數狀況下都是很是好的離線優先例子,但在這點上卻有點小問題。

若是沒有網絡鏈接,Twitter沒法提供與推送消息相關的內容。可是點按通知會移除通,從而使用戶獲取的信息比點按通知以前還要少。不要這麼作!

下面的代碼會在展現通知以前更新緩存。

self.addEventListener('push', (event) => {
  if (event.data.text() == 'new-email') {
    event.waitUntil(async function() {
      const cache = await caches.open('mysite-dynamic');
      const response = await fetch('/inbox.json');
      await cache.put('/inbox.json', response.clone());
      const emails = await response.json();
      registration.showNotification("New email", {
        body: "From " + emails[0].from.name
        tag: "new-email"
      });
    }());
  }
});

self.addEventListener('notificationclick', function(event) {
  if (event.notification.tag == 'new-email') {
    // Assume that all of the resources needed to render
    // /inbox/ have previously been cached, e.g. as part
    // of the install handler.
    new WindowClient('/inbox/');
  }
});

後臺同步時

圖片描述

注意: 後臺同步還沒有加入到Chrome穩定版本中。(譯者注:Chrome 49及以後的版本中開始支持,但FireFox、Safari還沒有支持,更多信息請參考 can i use

後臺同步是基於ServiceWorker來構建的另外一個功能。它容許您一次性地,或者按照(很是具備啓發性的)時間間隔來請求後臺數據同步。即便用戶沒有爲您的站點打開一個標籤,後臺同步也一樣能夠工做。只有ServiceWorker被喚醒。您從頁面請求執行此操做權限的同時,用戶也將收到提示。

適用於: 不緊急的更新,尤爲是那些按期進行的更新,每次更新都發送一個推送消息顯得太頻繁,好比社交時間表和新聞資訊。

相關文章
相關標籤/搜索