美文网首页
PWA做离线数据缓存实现与探索

PWA做离线数据缓存实现与探索

作者: Happiness9 | 来源:发表于2020-11-25 16:04 被阅读0次

    PWA

    Service workerPWA得以实现的核心技术

    service worker

    Service worker 是一个独立的worker线程,独立于当前网页进程,是一种特殊的web worker。主要功能在生命周期函数中实现。

    service worker注册

    if('serviceWorker' in navigator) {
      const sw = await navigator.serviceWorker.register(serviceWorker文件路径);
    }
    //使用serviceworker-webpack-plugin插件注册方式
    import runtime from 'serviceworker-webpack-plugin/lib/runtime'
    if('serviceWorker' in navigator) {
      const sw = await runtime.register();
    }
    

    serviceworker-webpack-plugin插件可以将所有打包后的目录文件注入到打包后的sw.js文件,通过global.serviceWorkerOptions.assets获取所有目录文件名,便于做静态资源的缓存。

    PWA用到的service worker生命周期函数

    • install缓存所有你需要的静态资源

      self.addEventListener('install', async () => {
        console.log('service worker installing');
        const cache = await caches.open(CACHE_NAME); //cacheStorage中缓存的名称
        const CACHE_URL = global.serviceWorkerOptions.assets.concat(['/']); //缓存的静态资源目录,不要忘记缓存'/'目录文件,在断网情况下,页面首先加载的是'/'目录资源。
        await cache.addAll(CACHE_URL); //此处只要有一个资源无法下载,静态资源的缓存都会失败
        await self.skipWaiting(); //跳过等待,保持运行最新的service worker
      })
      
    • active删除旧的缓存

      self.addEventListener('activate', async () => {
        console.log('service worker activate');
        const cacheKeys = await caches.keys();
        cacheKeys.map(async item => { //删除旧的缓存
          if (item !== CACHE_NAME) {
            await caches.delete(item);
          }
        })
        await self.clients.claim();//接管所有页面
      }
      
    • fetch可以拦截所有的请求,并做数据缓存

      self.addEventListener('fetch', async e => {
        console.log('service worker fetch');
        const req = e.request;
        const url = new URL(req.url);
        const api = new URL(apiHost); //自己使用的请求域名
        let isNetworkerFirst, isCacheFirst;
        if (isNetworkFirst) {
          e.respondWith(networkFirst(req)); //网络优先
        } else if (isCahceFirst) {
          e.respondWith(cacheFirst(req));//缓存优先
        }
      })
      
      const cacheFirst = async req => { //先从缓存中获取数据,如果没有匹配到,再发起网络请求
        const cache = await caches.open(CACHE_NAME);
        let cacheData = await cache.match(req);
        if (!cacheData) {
          cacheData = await fetch(req);
          if (!cacheData || cacheData.status !== 200) return cacheData;
          const cache = await caches.open(CACHE_NAME);
          cache.put(req, cacheData.clone());
        }
        return cacheData;
      };
      
      const networkFirst = async req => { //先发起网络请求,如果失败则再从缓存中匹配
        const cache = await caches.open(CACHE_NAME);
        let fetchResult;
        try {
          await Promise.race([requestPromise(req).then(res => {
            fetchResult = res;
            if (timer) clearTimeout(timer);
            if (isNetworkSlowly) isNetworkSlowly = false;
            hasShowNotification = false;
          }), timeout_promise()]);
          if (!fetchResult || fetchResult.status !== 200) return fetchResult;
          cache.put(req, fetchResult.clone());
          return fetchResult;
        } catch (e) {
          const cacheData = await cache.match(req);
          if (navigator.onLine && cacheData && e === 'request timeout' && isNetworkSlowly && !hasShowNotification) {
            showLocalNotification('网络不给力,当前访问的是缓存数据');
            isNetworkSlowly = false;
            hasShowNotification = true;
          }
          console.log(e, cacheData, 'error')
          return cacheData;
        }
      }
      
      //设置一定时间,在原本的fetch请求还没有响应的情况下,让service worker中的fetch报出’request timeout‘错误,从而转向向cache中匹配请求资源,实现在弱网情况下的网页正常浏览
      const timeout_promise = () => {
        return new Promise((resolve, reject) => {
          timer = setTimeout(() => {
            if (!isNetworkSlowly) isNetworkSlowly = true;
            reject('request timeout');
          }, 9000);
        });
      } 
      
      const showLocalNotification = (title, body) => {
        const options = {};
        try {
          self.registration.showNotification(title, options);
        } catch (error) {
          console.warn(error);
        }
      };
      

      根据自己的需求选择网络优先还是缓存优先,例如isNetworkFirst = url.origin === self.origin && req.method === 'GET'e.respondWith()对拦截的请求,把缓存匹配的或者网络请求到的数据返回,作出最后的响应。self.registration.showNotification()向浏览器发送消息。

    其他相关

    • 缓存使用到的cacheStorage可见详情文档

    • 浏览器可以通过addEventListener('offline', () => {})addEventListener('online', () => {})来监听浏览器网络在线与离线状态。但无法判断弱网状态,弱网状态请求缓存数据的具体实现也可根据自己的项目逻辑来定(例如:请求超过10秒还未得到响应,判定为弱网状态)。

    • service worker 注册后,对静态资源的下载缓存会占用部分带宽,影响项目首页的加载速度,可以设置一个定时器,在一定的时间后才启动注册程序。

    • cacheStorage无法缓存POST请求的数据。

    • 向浏览器发送消息,首先需要获取相应的权限。permission = await window.Notification.requestPermission()获取浏览器发送提醒消息权限,permission = 'granted'时,允许发送消息。

    • 使用self.skipWaiting()可以保证执行最新的sw,但新旧sw的交替,往往都要经过service workerinstall->waiting->active,因此总会有页面前后期由不同的sw来处理的问题。

    相关文章

      网友评论

          本文标题:PWA做离线数据缓存实现与探索

          本文链接:https://www.haomeiwen.com/subject/apyniktx.html