Caching - Progressive Web Apps

Storage Options

  • IndexedDB(原生API过于复杂)
  • Web SQL
  • Local Storage
  • Cache Storage API
  • pouchDB(Based on CouchDB)
  • localForage(自动帮你选择IndexedDB,Web SQL,Local Storage)
  • Lovefield

Cache Storage API

caches.open() caches.has() caches.delete()

save {Request: Response} pair to cache

完整可用的Service Worker Cache Example

main.js

// Progressive Enhancement (SW supported)
// if ('serviceWorker' in navigator) {
if (navigator.serviceWorker) {

  // Register the SW
  navigator.serviceWorker.register('/sw.js').then((registration) => {

  }).catch(console.log);
}

sw.js

// Service Worker
const pwaCache = 'pwa-cache-2';
self.addEventListener('install', (e) => {
  let cacheReady = caches.open(pwaCache).then((cache) => {
    console.log('New cache ready.');
    return cache.addAll([
      '/',
      'style.css',
      'thumb.png',
      'main.js'
    ]);
  });
  e.waitUntil(cacheReady);
});


self.addEventListener('activate', (e) => {
  let cacheCleaned = caches.keys().then((keys) => {
    keys.forEach((key) => {
      if( key !== pwaCache ) return caches.delete(key);
    });
  });
  e.waitUntil(cacheCleaned);
});

// 此处可以使用下面任意一个Cache策略
self.addEventListener('fetch', (e) => {
  // Skip for remote fetch
  if ( !e.request.url.match(location.origin) )  return;
  // Serve local fetch from cache
  let newRes = caches.open(pwaCache).then((cache) => {
    return cache.match(e.request).then((res) => {
      // Check request was found in cache
      if (res) {
        console.log(`Serving ${res.url} from cache.`);
        return res;
      }
      // Fetch on behalf of client and cache
      return fetch(e.request).then((fetchRes) => {
        cache.put(e.request, fetchRes.clone());
        return fetchRes;
      });
    });
  });
  e.respondWith(newRes);

});

Caching Strategies

1. Cache Only

只使用Cache,如果Cache中没有该资源,或者Cache被清理,则会失败。这仅使用与纯本地应用。

// 1. Cache only. Static assets - App Shell
self.addEventListener('fetch', (e) => {
  e.respondWith(caches.match(e.request));
})

2. Cache with Network Fallback

适用于存储静态资源,对实时性要求不高的资源。例如图片,CSS等。

self.addEventListener('fetch', (e) => {

  // 2. Cache with Network Fallback
  e.respondWith(
    caches.match(e.request).then( (res) => {
      if(res) return res;

      // Fallback
      return fetch(e.request).then( (newRes) => {
        // Cache fetched response
        caches.open(pwaCache).then( cache => cache.put(e.request, newRes) );
        return newRes.clone();
      })
    })
  );
})

3. Network with cache fallback

在慢速网络下,不一定是一个好的方案。

self.addEventListener('fetch', (e) => {
  e.respondWith(
    fetch(e.request).then( (res) => {
      // Cache latest version
      caches.open(pwaCache).then( cache => cache.put(e.request, res) );
      return res.clone();
  
    // Fallback to cache
    }).catch( err => caches.match(e.request) )
  );
})

4. Cache with Network Update

在Workbox中该策略被称为Stale-While-Revalidate,The stale-while-revalidate pattern allows you to respond the request as quickly as possible with a cached response if available, falling back to the network request if it’s not cached. The network request is then used to update the cache.

// 4. Cache with Network Update
self.addEventListener('fetch', (e) => {
  e.respondWith(
    caches.open(pwaCache).then( (cache) => {
  
      // Return from cache
      return cache.match(e.request).then( (res) => {
  
        // Update
        let updatedRes = fetch(e.request).then( (newRes) => {
          // Cache new response
          cache.put(e.request, newRes.clone());
          return newRes;
        });
  
        return res || updatedRes;
      })
    })
  );
})

5. Cache & Network Race with offline content

self.addEventListener('fetch', (e) => {

  // 5. Cache & Network Race with offline content
  let firstResponse = new Promise((resolve, reject) => {

    // Track rejections
    let firstRejectionReceived = false;
    let rejectOnce = () => {
      if (firstRejectionReceived) {

        if (e.request.url.match('thumb.png')) {
          resolve(caches.match('/placeholder.png'));
        } else {
          reject('No response received.')
        }
      } else {
        firstRejectionReceived = true;
      }
    };

    // Try Network
    fetch(e.request).then( (res) => {
      // Check res ok
      res.ok ? resolve(res) : rejectOnce();
    }).catch(rejectOnce);

    // Try Cache
    caches.match(e.request).then( (res) => {
      // Check cache found
      res ? resolve(res) : rejectOnce();
    }).catch(rejectOnce);

  });
  e.respondWith(firstResponse);

})

Resources

相关文章: