Service Worker实践:缓存股票信息

2017-10-23

Service worker可以充当Web应用程序与浏览器之间的代理服务器,常用于拦截网络请求,优化离线体验。最近开发一个功能需要用到股票信息,这些信息在每个交易日都是固定不变的,可以在前端缓存起来,减少重复请求。于是引入了Service workers。


由于Service workers只支持https,且在IE和Safari不无能为力,因此得先写一套基于传统localStorage的缓存方案,大概像这样:

let stockData = [];  //缓存的股票信息
  const stockCacheExpiration = 1;  //缓存过期的天数
  
  function getStockInfo(){
    // 如果缓存里有信息且不过期,则直接读取缓存,够则请求新的信息
    let stockCache = localStorage.getItem('stock_info');
    if (stockCache) {
      let stockInfo = JSON.parse(stockCache);
      let today = getDate();
      if (today-stockInfo.date < stockCacheExpiration) {
        stockData = stockInfo.data;
      } else {
        refreshStockInfo();
      }
    } else {
      refreshStockInfo();
    };
  }
  
  function refreshStockInfo(){
    // 请求股票信息,返回可以交易的股票信息的数组
    fetch('api/stock.json', {
      method'GET',
      headers: {
        'Accept''application/json',
        'Content-Type''application/json',
      }
    })
    .then(function (response) {
      if (response.status == 200) {
        return response.json()
      }
    })
    .then(function (data) {
    	stockData = data;
      let stockInfo = {
        dategetDate(),
        data: data
      };
      localStorage.setItem('stock_info'JSON.stringify(stockInfo));
    })
    .catch(function (error) {
      console.error(error)
    });
  }
  
  function getDate(){
    // 获取当前日期函数,省略
    return '20171010'
  }

省略不重要的内容,但能说清楚需求:如果缓存在有效期内,就直接读取缓存信息;如果没有有效缓存,fetch发起get请求。


下面进入正题,用Service Worker拦截请求。这样业务代码里不需要再判断是否有缓存,直接请求就对了,缓存的事情完全交给Service Worker去实现。首先判断浏览器支持,然后引入serviceWorker:

if ('serviceWorker' in navigator) {
    navigator.serviceWorker.register('sw.js').then(function(registration) {
      console.log('ServiceWorker registration successful with scope: ',    registration.scope);
    }).catch(function(err) {
      console.log('ServiceWorker registration failed: ', err);
    });
  }

如果sw.js已经被注册过,浏览器会自动忽略上面的代码。用Chrome浏览器,可以到 chrome://inspect/#service-workers 查看启用情况。sw.js就是我们要调用的serviceWorker了,用来实现缓存,拦截fetch并返回缓存的内容。核心代码如下:

const urlsToCache = [
    'api/stock.json'
  ];
  
  // serviceWorker安装的时候缓存股票信息
  self.addEventListener('install'function(event) {
    event.waitUntil(
      caches.open('stock')
      .then(function(cache) {
      console.log('Opened cache');
      return cache.addAll(urlsToCache);
      })
    );
  });
  
  // 拦截fetch请求,并返回缓存的内容
  self.addEventListener('fetch'function(event) {
    event.respondWith(
      caches.match(event.request)
        .then(function(response) {
          if (response) {
            return response;
          }
          return fetch(event.request);
        }
      )
    );
  });

这里我们用Cache进行缓存,这是Service Worker衍生出来的API,并不直接缓存字符串(想想localstorage),而是直接缓存资源请求(css、js、html、json等)。缓存的key就是request,value就是response。


在Chrome调试台的Network里查看请求,可以看到Status Code:200 OK (from ServiceWorker),表示这个请求是ServiceWorker处理过的。把缓存逻辑提取出来之后,业务代码就非常简单了,只需要在需要用到的地方直接发出请求就行了,不需要管缓存的事情。这样也可以避免代码污染,让业务代码变得更加清晰。

Service Worker使用起来还是有许多麻烦,首先是过期机制,需要关掉页面重新打开才能调用到install,调试过程比较麻烦。其次权限非常大,为了安全仅支持https,跨域也很复杂。最重要的是IE不配合,所以还得做一套兼容,代码就更加不优雅了。


本文未经许可禁止转载,如需转载关注微信公众号【工程师加一】并留言。