Service Workerのキャッシュ管理


導入

Service Workerとは、ページ上のスクリプトと並行して読み込まれ、ウェブページのコンテキスト外で実行されるスクリプトのことです。Service Worker は DOM やユーザー インタラクションにはアクセスできませんが、postMessage API を介してスクリプトと通信することができます。これは Web アプリのプロキシとして動作し、すべてのサーバのリクエストを傍受し、例えばキャッシュを使って応答したり、LocalStorageIndexedDB からデータを取得したりすることを提案します。そのため、アプリケーションをオフラインで利用できるようになります。

Service Workerのライフサイクル

Service workerを有効に活用するためには、サービスライフサイクルの理解が不可欠です。

Service Workerのライフサイクルは、主に3つのフェーズで構成されています。


  • 登録(Registration)

  • インストール(Installation)

  • アクティベーション(Activation)


それぞれを見ていきましょう。

登録


最初のブロックは、以下のコードでサービスワーカーを登録します。
if ('serviceWorker' in navigator) {
  navigator.serviceWorker.register('/service-worker/sw.js', { scope: '/sw-test/' }).then(function(reg) {
    // 登録がうまくいった
    console.log('Registration succeeded. Scope is ' + reg.scope);
  }).catch(function(error) {
    // 登録に失敗しました
    console.log('Registration failed with ' + error);
  });
};

Service Worker スクリプトは /service-worker/sw.js にあります。
次の段落で詳しく見ていきましょう。

インストール


Servie workerが呼ばれて初めてブラウザにインストールされます。
this.addEventListener('install', function(event) {
 event.waitUntil(
  // キャッシュにファイルを追加する
    caches.open(expectedCacheNames).then(cache => cache.addAll([
        'index.html',
        'style.css',
        'app.js',
        'cat.png',
]))
  );
});

event.waitUntil は、パラメータで渡されたPromiseの解決(または拒否)まで他のイベントをブロックすることができます。Service Workerは、キャッシュされる Request/Response オブジェクトのペアを表現するためのキャッシュインターフェイスを提供します。

アクティベーション

activate イベントはService WorkerがRequestの処理を担当する直前に発行されるので、不要なキャッシュを削除するに良いタイミングです。

this.addEventListener('activate', function(event) {
  event.waitUntil(
    caches.keys().then(function(cacheNames) {
      return Promise.all(cacheNames.map(function(cacheName) {
        if (expectedCacheNames.indexOf(cacheName) === -1) {
          //期限切れのキャッシュの削除
          return caches.delete(keyList[i]);
        }
      }));
    })
  );
});

Fetch イベント 

Requestを傍受するために、Fetchイベントに動作を追加します。

this.addEventListener('fetch', function(event) {
  // ここでマジックが起こります!
});

event.respondWith は、カスタムレスポンスを記録したり、ネットワークエラーを処理したりすることができます。そして、キャッシュが利用可能であれば、リクエストに対応するリソースを返すだけです。

let response;
event.respondWith(caches.match(event.request).catch(function() {
  return fetch(event.request);
}).then(function(r) {
  response = r;
  caches.open(expectedCacheNames).then(function(cache) {
    cache.put(event.request, response);
  });
  return response.clone();
}));

リクエストオブジェクトとレスポンスオブジェクトは一度しか消費できないストリームなので、レスポンスのコピーを返します。
最後に、後者のケースでは、リクエストがキャッシュにマッチせずネットワークが利用できない場合、デフォルトのリソースを返します。

catch(function() {
  return caches.match('404.jpg');
})

以上