Service Worker with Multiple Caches

2019-04-01 11:59发布

问题:

There are numerous examples out there for initializing a service worker with a single cache similar to the following:

let cacheName = 'myCacheName-v1';
let urlsToCache = ['url1', 'url2', url3'];

self.addEventListener('install', function (event) {
  event.waitUntil(
    caches.open(cacheName).then(function (cache) {
      return cache.addAll(urlsToCache);
    }).then(function () {
      return this.skipWaiting();
    })
  );
});

I wish to initialize multiple caches on my service worker. The motivation is to group assets by their tendency for change (e.g., static app data vs. css, javascript, etc.). With multiple caches, I can update individual caches (via versioned cache names) as files within that cache change. Ideally, I wish to setup a structure similar to the following:

let appCaches = [{
    name: 'core-00001',
    urls: [
      './',
      './index.html', etc...
    ]
  },
  {
    name: 'data-00001',
    urls: [
      './data1.json',
      './data2.json', etc...
    ]
  },
  etc...
];

My best attempt so far is something similar to:

self.addEventListener('install', function (event) {
  appCaches.forEach(function (appCache) {
    event.waitUntil(
      caches.open(appCache.name).then(function (cache) {
        return cache.addAll(appCache.urls);
      }));
  });
  self.skipWaiting();
});

This approach seems to work. However, I'm still a newbie to service workers and promises. Something tells me that this approach has a pitfall that I'm too inexperienced to recognize. Is there a better way to implement this?

回答1:

It's better to call event.waitUntil only once in a handler, but the good thing is it's relatively easy to get a single Promise to wait for.

Something like that should work:

event.waitUntil(Promise.all(
  myCaches.map(function (myCache) {
    return caches.open(myCache.name).then(function (cache) {
      return cache.addAll(myCache.urls);
    })
  )
));

Promise.all takes an Array of Promises and resolves only after all the promises in the Array resolve, which means that install handler will wait till all the caches are initialized.



回答2:

Thanks pirxpilot! Your answer, combined with JavaScript Promises: an Introduction, and a lot of trial and error figuring out how to chain all these Promises together resulted in a nice little implementation.

I added a requirement to only cache changes. Per best practices, the old cache is deleted during the 'activate' event. The end product is:

let cacheNames = appCaches.map((cache) => cache.name);

self.addEventListener('install', function (event) {
  event.waitUntil(caches.keys().then(function (keys) {
    return Promise.all(appCaches.map(function (appCache) {
      if (keys.indexOf(appCache.name) === -1) {
        return caches.open(appCache.name).then(function (cache) {
          console.log(`caching ${appCache.name}`);
          return cache.addAll(appCache.urls);
        })
      } else {
        console.log(`found ${appCache.name}`);
        return Promise.resolve(true);
      }
    })).then(function () {
      return this.skipWaiting();
    });
  }));
});

self.addEventListener('activate', function (event) {
  event.waitUntil(
    caches.keys().then(function (keys) {
      return Promise.all(keys.map(function (key) {
        if (cacheNames.indexOf(key) === -1) {
          console.log(`deleting ${key}`);
          return caches.delete(key);
        }
      }));
    })
  );
});