渐进式web应用开发--拥抱离线优先(三)

阅读目录javascript

一:什么是离线优先?php

传统的web应用彻底依赖于服务器端,好比像很早之前jsp,php,asp时代,全部的数据,内容和应用逻辑都在服务器端,客户端仅仅作一些html内容渲染到页面上去。可是随着技术在不断的改变,如今不少业务逻辑也放在前端,先后端分离,前端是作模板渲染工做,后端只作业务逻辑开发,只提供数据接口。可是咱们的web前端开发在数据层这方面来说仍是依赖于服务器端。若是网络中断或服务器接口挂掉了,都会影响数据页面展现。所以咱们须要使用离线优先这个技术来更优雅的处理这个问题。css

拥抱离线优先的真正含义是:尽管应用程序的某些功能在用户离线时可能不能正常使用,可是更多的功能应该保持可用状态。html

离线优先它能够优雅的处理这些异常状况下问题,当用户离线时,用户正在查看数据多是以前的数据,可是仍然能够访问以前的页面,以前的数据不会丢失,这就意味着用户能够放心使用某些功能。那么要作到离线时候还能够访问,就须要咱们缓存哦。前端

二:经常使用的缓存模式java

在为咱们的网站使用缓存以前,咱们须要先熟悉一些常见的缓存设计模式。若是咱们要作一个股票K线图的话,由于股票数据是实时更新的,所以咱们须要实时的去请求网络最新的数据(固然实时确定使用websocket技术,而不是http请求,我这边是假如)。只有当网络请求失败的时候,咱们再从缓存里面去读取数据。可是对于股票K线图中的一些图标展现这样的,由于这些图标是通常不会变的,因此咱们更倾向于使用缓存里面的数据。只有在缓存里面找不到的状况下,再从网络上请求数据。node

因此有以下几种缓存模式:webpack

1. 仅缓存
2. 缓存优先,网络做为回退方案。
3. 仅网络。
4. 网络优先,缓存做为回退方案。
5. 网络优先,缓存做为回退方案, 通用回退。git

1. 仅缓存github

什么是仅缓存呢?仅缓存是指 从缓存中响应全部的资源请求,若是缓存中找不到的话,那么请求就会失败。那么仅缓存对于静态资源是实用的。由于静态资源通常是不会发生变化,好比图标,或css样式等这些,固然若是css样式发生改变的话,在后缀能够加上时间戳这样的。好比 base.css?t=20191011 这样的,若是时间戳没有发生改变的话,那么咱们直接从缓存里面读取。
所以咱们的 sw.js 代码能够写成以下(注意:该篇文章是在上篇文章基础之上的,若是想看上篇文章,请点击这里

self.addEventListener("fetch", function(event) {
  event.respondWith(
    caches.match(event.request)
  )
});

如上代码,直接监听 fetch事件,该事件能监听到页面上全部的请求,当有请求过来的时候,它使用缓存里面的数据依次去匹配当前的请求,若是匹配到了,就拿缓存里面的数据,若是没有匹配到,则请求失败。

2. 缓存优先,网络做为回退方案

该模式是:先从缓存里面读取数据,当缓存里面没有匹配到数据的时候,service worker才会去请求网络并返回。

代码变成以下:

self.addEventListener("fetch", function(event) {
  event.respondWith(
    caches.match(event.request).then(function(response) {
      return response || fetch(event.request);
    })
  )
});

如上代码,使用fetch去监听全部请求,而后先使用缓存依次去匹配请求,不论是匹配成功仍是匹配失败都会进入then回调函数,当匹配失败的时候,咱们的response值就为 undefined,若是为undefined的话,那么就网络请求,不然的话,从拿缓存里面的数据。

3. 仅网络

传统的web模式,就是这种模式,从网络里面去请求,若是网络不通,则请求失败。所以代码变成以下:

self.addEventListener("fetch", function(event) {
  event.respondWith(
    fetch(event.request)
  )
});

4. 网络优先,缓存做为回退方案。

先从网络发起请求,若是网络请求失败的话,再从缓存里面去匹配数据,若是缓存里面也没有找到的话,那么请求就会失败。

所以代码以下:

self.addEventListener("fetch", function(event) {
  event.respondWith(
    fetch(event.request).catch(function() {
      return caches.match(event.request);
    })
  )
});

5. 网络优先,缓存做为回退方案, 通用回退

该模式是先请求网络,若是网络失败的话,则从缓存里面读取,若是缓存里面读取失败的话,咱们提供一个默认的显示给页面展现。

好比显示一张图片。以下代码:

self.addEventListener("fetch", function(event) {
  event.respondWith(
    fetch(event.request).catch(function() {
      return caches.match(event.request).then(function(response) {
        return response || caches.match("/xxxx.png");
      })
    });
  )
});

三:混合与匹配,创造新模式

上面是咱们五种缓存模式。下面咱们须要将这些模式要组合起来使用。

1. 缓存优先,网络做为回退方案, 并更新缓存。

对于不常常改变的资源,咱们能够先缓存优先,网络做为回退方案,第一次请求完成后,咱们把请求的数据缓存起来,下次再次执行的时候,咱们先从缓存里面读取。

所以代码以下:

self.addEventListener("fetch", function(event) {
  event.respondWith(
    caches.open("cache-name").then(function(cache) {
      return cache.match(event.request).then(function(cachedResponse){
        return cachedResponse || fetch(event.request).then(function(networkResponse){
          cache.put(event.request, networkResponse.clone());
          return networkResponse;
        });
      })
    })
  )
});

如上代码,咱们首先打开缓存,而后使用请求匹配缓存,无论匹配成功了仍是匹配失败了,都会进入then回调函数,若是匹配到了,说明缓存里面有对应的数据,那么直接从缓存里面返回,若是缓存里面 cachedResponse 值为undefined,没有的话,那么就从新使用fetch请求网络,而后把请求的数据 networkResponse 从新返回回来,而且克隆一份 networkResponse 放入缓存里面去。

2. 网络优先,缓存做为回退方案,并频繁更新缓存

若是一些常常要实时更新的数据的话,好比百度上的一些实时新闻,那么都须要对网络优先,缓存做为回退方案来作,那么该模式下首先会从网络中获取最新版本,当网络请求失败的时候才回退到缓存版本,当网络请求成功的时候,它会将当前返回最新的内容从新赋值给缓存里面去。这样就保证缓存永远是上一次请求成功的数据。即便网络断开了,仍是会使用以前最新的数据的。

所以代码能够变成以下:

self.addEventListener("fetch", function(event) {
  event.respondWith(
    caches.open("cache-name").then(function(cache) {
      return fetch(event.request).then(function(networkResponse) {
        cache.put(event.request, networkResponse.clone());
        return networkResponse;
      }).catch(function() {
        return caches.match(event.request);
      });
    })
  )
});

如上代码,咱们使用fetch事件监听全部的请求,而后打开缓存后,咱们先请网络请求,请求成功后,返回最新的内容,此时此刻同时把该返回的内容克隆一份放入缓存里面去。可是当网络异常的状况下,咱们就匹配缓存里面最新的数据。可是在这种状况下,若是咱们第一次网络请求失败后,因为第一次咱们没有作缓存,所以缓存也会失败,最后就会显示失败的页面了。

3. 缓存优先,网络做为回退方案,并频繁更新缓存

对于一些常常改变的资源文件,咱们能够先缓存优先,而后再网络做为回退方案,也就是说先缓存里面找到,也总会从网络上请求资源,这种模式能够先使用缓存快速响应页面,同时会从新请求来获取最新的内容来更新缓存,在咱们用户下次请求该资源的时候,那么它就会拿到缓存里面最新的数据了,这种模式是将快速响应和最新的响应模式相结合。

所以咱们的代码改为以下:

self.addEventListener('fetch', function(event) {
  event.respondWith(
    caches.open("cache-name").then(function(cache) {
      return cache.match(event.request).then(function(cachedResponse) {
        var fetchPromise = fetch(event.request).then(function(networkResponse) {
          cache.put(event.request, networkResponse.clone());
          return networkResponse;
        });
        return cachedResponse || fetchPromise;
      });
    })
  )
});

如上代码,咱们首先打开一个缓存,而后咱们试图匹配请求,不论是否匹配成功,咱们都会进入then函数,在该回调函数内部,会先从新请求一下,请求成功后,把最新的内容返回回来,而且以此同时把该请求数的数据克隆一份出来放入缓存里面去。最后把请求的资源文件返回保存到 fetchPromise 该变量里面,最后咱们先返回缓存里面的数据,若是缓存里面没有数据,咱们再返回网络fetchPromise 返回的数据。

如上就是咱们3种常见的模式。下面咱们就须要来规划咱们的缓存策略了。

四:规划缓存策略

在咱们以前讲解的demo中(https://www.cnblogs.com/tugenhua0707/p/11148968.html), 都是基于网络优先,缓存做为回退方案模式的。咱们以前使用这个模式给用户体验仍是挺不错的,首先先请求网络,当网络断开的时候,咱们从缓存里面拿到数据。
这样就不会使页面异常或空白。可是上面咱们已经了解到了缓存了,咱们能够再进一步优化了。

咱们如今可使用离线优先的方式来构建咱们的应用程序了,对应咱们项目常常会改变的资源咱们优先使用网络请求,若是网络不能够用的话,咱们使用缓存里面的数据。

首先仍是看下咱们项目的整个目录结构以下:

|----- 项目
|  |--- public
|  | |--- js               # 存放全部的js
|  | | |--- main.js        # js入口文件
|  | |--- style            # 存放全部的css
|  | | |--- main.styl      # css 入口文件
|  | |--- index.html       # index.html 页面
|  | |--- images
|  |--- package.json
|  |--- webpack.config.js
|  |--- node_modules
|  |--- sw.js

咱们的首页 index.html 代码以下:

<!DOCTYPE html>
<html lang="en">
<head>
  <meta charset="UTF-8">
  <title>service worker 实列</title>
  <link rel="stylesheet" href="/main.css" />
</head>
<body>
  <div id="app">22222</div>
  <img src="/public/images/xxx.jpg" />
  <script type="text/javascript" src="/main.js"></script>
</body>
</html>

首页是由静态的index.html 组成的,它通常不多会随着版本的改变而改变的,它页面中会请求多个图片,请求多个css样式,和请求多个js文件。在index.html中全部的静态资源文件(图片、css、js)等在咱们的service worker安装过程当中会缓存下来的,那么这些资源文件适合的是 "缓存优先,网络做为回退方案" 模式来作。这样的话,页面加载会更快。

可是index.html呢?这个页面通常状况下不多改变,咱们通常会想到 "缓存优先,网络做为回退方案" 来考虑,可是若是该页面也改动了代码呢?咱们若是一直使用缓存的话,那么咱们就得不到最新的代码了,若是咱们想咱们的index.html拿到最新的数据,咱们不得不从新更新咱们的service worker,来获取最新的缓存文件。可是咱们从以前的知识点咱们知道,在咱们旧的service worker 释放页面的同时,新的service worker被激活以前,页面也不是最新的版本的。必需要等第二次从新刷新页面的时候才会看到最新的页面。那么咱们的index.html页面要如何作呢?

1) 若是咱们使用 "缓存优先,网络做为回退方案" 模式来提供服务的话,那么这样作的话,当咱们改变页面的时候,它就有可能不会使用最新版本的页面。

2)若是咱们使用 "网络优先,缓存做为回退方案 " 模式来作的话,这样确实能够经过请求来显示最新的页面,可是这样作也有缺点,好比咱们的index.html页面没有改过任何东西的话,也要从网络上请求,而不是从缓存里面读取,致使加载的时间会慢一点。

3) 使用 缓存优先,网络做为 回退方案,并频繁更新缓存模式。该模式老是从缓存里面读取 index.html页面,那么它的响应时间相对来讲是很是快的,而且从缓存里面读取页面后,咱们同时会请求下,而后返回最新的数据,咱们把最新的数据来更新缓存,所以咱们下一次进来页面的时候,会使用最新的数据。

所以对于咱们的index.html页面,咱们适合使用第三种方案来作。

所以对于咱们这个简单的项目来说,咱们能够总结以下:

1. 使用 "缓存优先,网络做为回退方案,并频繁更新缓存" 模式来返回index.html文件。
2. 使用 "缓存优先,网络做为回退方案" 来返回首页须要的全部静态文件。

所以咱们可使用上面两点,来实现咱们的缓存策略。

五:实现缓存策略

如今咱们来更新下咱们的 sw.js 文件,该文件来缓存咱们index.html,及在index.html使用到的全部静态资源文件。

index.html 代码改为以下:

<!DOCTYPE html>
<html lang="en">
<head>
  <meta charset="UTF-8">
  <title>service worker 实列</title>
</head>
<body>
  <div id="app">22222</div>
  <img src="/public/images/xxx.jpg" />
</body>
</html>

js/main.js 代码变为以下:

// 加载css样式
require('../styles/main.styl');

if ("serviceWorker" in navigator) {
  navigator.serviceWorker.register('/sw.js', {scope: '/'}).then(function(registration) {
    console.log("Service Worker registered with scope: ", registration.scope);
  }).catch(function(err) {
    console.log("Service Worker registered failed:", err);
  });
}

sw.js 代码变成以下:

var CACHE_NAME = "cacheName";

var CACHE_URLS = [
  "/public/index.html",      // html文件
  "/main.css",               // css 样式表
  "/public/images/xxx.jpg",  // 图片
  "/main.js"                 // js 文件 
];

// 监听 install 事件,把全部的资源文件缓存起来
self.addEventListener("install", function(event) {
  event.waitUntil(
    caches.open(CACHE_NAME).then(function(cache) {
      return cache.addAll(CACHE_URLS);
    })
  )
});

// 监听fetch事件,监听全部的请求

self.addEventListener("fetch", function(event) {
  var requestURL = new URL(event.request.url);
  console.log(requestURL);
  if (requestURL.pathname === '/' || requestURL.pathname === "/index.html") {
    event.respondWith(
      caches.open(CACHE_NAME).then(function(cache) {
        return cache.match("/index.html").then(function(cachedResponse) {
          var fetchPromise = fetch("/index.html").then(function(networkResponse) {
            cache.put("/index.html", networkResponse.clone());
            return networkResponse;
          });
          return cachedResponse || fetchPromise;
        })
      })
    )
  } else if (CACHE_URLS.includes(requestURL.href) || CACHE_URLS.includes(requestURL.pathname)) {
    event.respondWith(
      caches.open(CACHE_NAME).then(function(cache) {
        return cache.match(event.request).then(function(response) {
          return response || fetch(event.request);
        });
      })
    )
  } 
});

self.addEventListener("activate", function(e) {
  e.waitUntil(
    caches.keys().then(function(cacheNames) {
      return Promise.all(
        cacheNames.map(function(cacheName) {
          if (CACHE_NAME !== cacheName && cacheName.startWith("cacheName")) {
            return caches.delete(cacheName);
          }
        })
      )
    })
  )
});

如上代码中的fetch事件,var requestURL = new URL(event.request.url);console.log(requestURL); 打印信息以下所示:

如上咱们使用了 new URL(event.request.url) 来决定如何处理不一样的请求。且能够获取到不一样的属性,好比host, hostname, href, origin 等这样的信息到。

如上咱们监听 fetch 事件中全部的请求,判断 requestURL.pathname 是不是 "/" 或 "/index.html", 若是是index.html 页面的话,对于 index.html 的来讲,使用上面的原则是:使用 "缓存优先,网络做为回退方案,并频繁更新缓存", 因此如上代码,咱们首先打开咱们的缓存,而后使用缓存匹配 "/index.html",无论匹配是否成功,都会进入then回调函数,而后把缓存返回,在该函数内部,咱们会从新请求,把请求最新的内容保存到缓存里面去,也就是说更新咱们的缓存。当咱们第二次访问的时候,使用的是最新缓存的内容。

若是咱们请求的资源文件不是 index.html 的话,咱们接着会判断下,CACHE_URLS 中是否包含了该资源文件,若是包含的话,咱们就从缓存里面去匹配,若是缓存没有匹配到的话,咱们会从新请求网络,也就是说咱们对于页面上全部静态资源文件话,使用 "缓存优先,网络做为回退方案" 来返回首页须要的全部静态文件。

所以咱们如今再来访问咱们的页面的话,以下所示:

如上所示,咱们能够看到,咱们第一次请求的时候,加载index.html 及 其余的资源文件,咱们能够从上图能够看到 加载时间的毫秒数,虽然从缓存里面读取第一次数据后,可是因为咱们的index.html 老是会请求下,把最新的资源再返回回来,而后更新缓存,所以咱们能够看到咱们第二次加载index.html 及 全部的service worker中的资源文件,能够看到第二次的加载时间更快,而且当咱们修改咱们的index.html 后,咱们刷新下页面后,第一次仍是从缓存里面读取最新的数据,当咱们第二次刷新的时候,页面才会显示咱们刚刚修改的index.html页面的最新页面了。所以就验证了咱们以前对于index.html 处理的逻辑。

使用 缓存优先,网络做为 回退方案,并频繁更新缓存模式。该模式老是从缓存里面读取 index.html页面,那么它的响应时间相对来讲是很是快的,而且从缓存里面读取页面后,咱们同时会请求下,而后返回最新的数据,咱们把最新的数据来更新缓存,所以咱们下一次进来页面的时候,会使用最新的数据。
github简单的demo

相关文章
相关标签/搜索