service-worker的基本知识

22次阅读

共计 6611 个字符,预计需要花费 17 分钟才能阅读完成。

前言:看到一篇讲解 service worker 的文章,基础讲的还不错,所以转了以后作为自己的参考

Service Worker 是什么

service worker 是独立于当前页面的一段运行在浏览器后台进程里的脚本。它的特性将包括推送消息, 背景后台同步,geofencing(地理围栏定位),拦截和处理网络请求。

这个 API 会让人兴奋的原因是,它可以使你的应用先访问本地缓存资源,所以在离线状态时,在没有通过网络接收到更多的数据前,仍可以提供基本的功能(一般称之为 Offline First)。

在 service worker 之前,另一个叫做 APP Cache 的 api 也可以提供离线体验。APP Cache 的的主要问题是坑比较多,而且其被设计为只适合于单页 web 应用程序,对于传统的多页网站则不适合。service worker 的设计规避了这些痛点。

关于 service worker 的一些注意点:

  • service worker 是一个 JavaScript worker , 所以它不能直接访问 DOM。但 service worker 可以通过 postMessage 接口与跟其相关的页面进行通信, 发送消息, 从而让这些页面在有需要的时候去操纵 DOM。
  • Service worker 是一个可编程的网络代理,允许你去控制如何处理页面的网络请求,可以处理 fetch 请求。
  • Service worker 在不使用时将被终止,并会在需要的时候重新启动,因此你不能把 onfetch 和 onmessage 事件来作为全局依赖处理程序。如果你需要持久话一些信息并在重新启动 Service worker 后使用他,可以使用 IndexedDBAPI,service worker 支持。
  • Service Worker 的缓存机制是依赖 Cache API 实现的 Service worker 广泛使用了 promise。
  • Service worker 依赖 HTML5 fetch API
  • Service Workers 要求必须在 HTTPS 下才能运行

Service Worker 生命周期

  1. 注册 service worker,在网页上生效
  2. 安装成功,激活 或者 安装失败(下次加载会尝试重新安装)
  3. 激活后,在 sw 的作用域下作用所有的页面,首次控制 sw 不会生效,下次加载页面才会生效。
  4. sw 作用页面后,处理 fetch(网络请求)和 message(页面消息)事件 或者 被终止(节省内存)。

需要提前掌握的 API

  • Cache API 基本使用
  • (1)检测 api 是否存在
if('caches' in window) {// Has support!}
  • (2)caches.open,创建缓存总对象。如下创建名为 test-cache 的缓存。
caches.open('test-cache').then(function(cache) {// Cache is created and accessible});
  • (3)cache.add 和 cache.addAll,添加缓存内容。其中 cache.add 只添加一个,cache.addAll 可以添加多个。
caches.open('test-cache').then(function(cache) {cache.addAll(['/', '/images/logo.png'])
        .then(function() { 
        // Cached!
        
        // or use cache.add
        cache.add('/page/1');  // "/page/1" URL will be fetched and cached!
     });
    });
  • (4)cache.keys(),查看已经缓存的数据
caches.open('test-cache').then(function(cache) {cache.keys().then(function(cachedRequests) {console.log(cachedRequests); // [Request, Request]
        });
    });
  • (5)cache.match 和 cache.matchAll,匹配缓存文件路径
caches.open('test-cache').then(function(cache) {cache.match('/page/1').then(function(matchedResponse) {console.log(matchedResponse);
        });
    });

(6)cache.delete,删除缓存。

caches.open('test-cache').then(function(cache) {cache.delete('/page/1');
   });

  • Fetch API 基本使用
// url (required), options (optional)
fetch('https://davidwalsh.name/some/url', {method: 'get'}).then(function(response) {}).catch(function(err) {// Error :(});

其中 options 对象包含以下属性:

  • method – GET, POST, PUT, DELETE, HEAD
  • url – 请求的链接
  • headers – 请求的 header 对象
  • referrer – 请求的 referrer 对象
  • mode – cors, no-cors, same-origin
  • credentials – 设置请求可不可以携带 cookie
  • redirect – follow, error, manual
  • integrity – 子资源完整值
  • cache – 缓存模式 (default, reload, no-cache)

可以在 fetch 中传入 Request 对象实例:

var request = new Request('https://davidwalsh.name/users.json', {
    method: 'POST',
    mode: 'cors',
    redirect: 'follow',
    headers: new Headers({'Content-Type': 'text/plain'})
});
 
// Now use it!
fetch(request).then(function() {/* handle response */});

可以自定义返回的 Response 对象实例,其中的 options 有:

  • type – basic, cors
  • url
  • useFinalURL – 上面的 url 参数是不是最终的 URL
  • status – 状态码 (ex: 200, 404, etc.)
  • ok – 是否成功响应 (范围在 200-299)
  • statusText – 状态码 (ex: OK)
  • headers – 响应的 headers 对象

另外 Response 的实例还具备以下方法:

  • clone() – 创建 Response 对象的克隆。
  • error() – 返回与网络错误关联的新 Response 对象。
  • redirect() – 使用不同的 URL 创建新响应。
  • arrayBuffer() – 返回使用 ArrayBuffer 解析的 promise。
  • blob() – 返回使用 Blob 解析的 promise。
  • formData() – 返回使用 FormData 对象解析的 promise。
  • json() – 返回使用 JSON 对象解析的 promise。
  • text() – 返回使用 USVString(文本)解析的 promise。
// Create your own response for service worker testing
// new Response(BODY, OPTIONS)
var response = new Response('.....', {
    ok: false,
    status: 404,
    url: '/'
});
 
// The fetch's `then` gets a Response instance back
fetch('https://davidwalsh.name/')
    .then(function(responseObj) {console.log('status:', responseObj.status);
    });

Service Worker 的使用

  1. 兼容低版本,注入 Cache API 的一个 polyfill,Service Worker 需要依赖 Cache API:
self.importScripts('./serviceworker-cache-polyfill.js');
  1. 注册 service worker:
if ('serviceWorker' in navigator) {navigator.serviceWorker.register('/sw.js').then(function(registration) {
    // Registration was successful
    console.log('ServiceWorker registration successful with scope:',    registration.scope);
  }).catch(function(err) {
    // registration failed :(console.log('ServiceWorker registration failed:', err);
  });
}

上面的代码检查 service worker API 是否可用,如果可用,/sw.js 这个文件将会作为 service worker 被注册。

如果这个 service worker 已经被注册过,浏览器会自动忽略上面的代码。

有一个特别要注意是 service worker 文件的路径。你一定注意到,在这个例子中,service worker 文件被放在这个域的根目录下,这意味着 service worker 是跟网站同源的。换句话说,这个 service worker 将会获取到这个域下的所有 fetch 事件。如果 service worker 文件注册到 /example/sw.js,那么 service worker 只能收到 /example/ 路径下的 fetch 事件(比如:/example/page1/, /example/page2/)。

  1. 安装 service worker:
var CACHE_NAME = 'my-site-cache-v1';
var urlsToCache = [
  '/',
  '/styles/main.css',
  '/script/main.js'
];
 
self.addEventListener('install', function(event) {
  // Perform install steps
  event.waitUntil(caches.open(CACHE_NAME)
      .then(function(cache) {console.log('Opened cache');
        return cache.addAll(urlsToCache);
      })
  );
});

上面代码声明了需要缓存的内容,如果所有的文件都缓存成功,service worker 就安装成功了。如果任何一个文件下载失败,那么安装步骤就会失败。这个方式依赖于你自己指定的资源,但这意味着,你需要非常仔细地确定哪些文件需要被缓存。指定了太多文件的话,会增加失败率。

  • 对缓存跟返回请求的处理
self.addEventListener('fetch', function(event) {
  event.respondWith(caches.match(event.request)
      .then(function(response) {
        // Cache hit - return response
        if (response) {return response;}
 
    // IMPORTANT: Clone the request. A request is a stream and
    // can only be consumed once. Since we are consuming this
    // once by cache and once by the browser for fetch, we need
    // to clone the response
    var fetchRequest = event.request.clone();
 
    return fetch(fetchRequest).then(function(response) {
        // Check if we received a valid response
        if(!response || response.status !== 200 || response.type !== 'basic') {return response;}
 
        // IMPORTANT: Clone the response. A response is a stream
        // and because we want the browser to consume the response
        // as well as the cache consuming the response, we need
        // to clone it so we have 2 stream.
        var responseToCache = response.clone();
 
        caches.open(CACHE_NAME)
          .then(function(cache) {cache.put(event.request, responseToCache);
          });
 
        return response;
      }
    );
  })
);

如果我们想在缓存中添加新的请求缓存,可以通过处理 fetch 请求的 response,将其添加到缓存中即可。代码里我们做了以下事情:

添加一个 callback 到 fetch 请求的 .then 方法中。一旦我们获得一个 response,我们进行如下的检查:

1. 确保 response 有效
2. 检查 response 的状态是 200
3. 确保 response 的类型是 basic 类型的,这说明请求是同源的,这意味着第三方的请求不能被缓存。

如果检查通过会 clone 这个请求。这么做的原因是如果 response 是一个 Stream,那么它的 body 只能被消费一次。所以为了让浏览器跟缓存都使用这个 body,我们必须克隆这个 body,一份到浏览器,一份到缓存中缓存。

重新激活

你的 service worker 总会有要更新的时候。在那时,你需要按照以下步骤来更新:

  • 更新你 service worker 的 JavaScript 文件 当用户浏览你的网站时,浏览器尝试在后台重新下载 service worker 的脚本文件。经过对比,只要服务器上的文件和本地文件有一个字节不同,这个文件就认为是新的。
  • 之后更新后的 service worker 启动并触发 install 事件。此时,当前页面生效的依然是老版本的 service worker,新的 service worker 会进入“waiting”状态。
  • 当页面关闭之后,老的 service worker 会被干掉,新的 servicer worker 接管页面 一旦新的 service worker 生效后会触发 activate 事件。通常来讲,需要在 activate 的 callback 中进行 cache 管理,来清理老的 cache。我们在 activate 而不是 install 的时候进行的原因,是如果我们在 install 的时候进行清理,那么老的 service worker 仍然在控制页面,他们依赖的缓存就失效了,因此就会突然被停止。

之前我们使用的缓存可以叫 my-site-cache-v1,我们想把这个拆封到多个缓存,一份给页面使用,一份给博客文章使用。这意味着,install 步骤里,我们要创建两个缓存:pages-cache-v1 和 blog-posts-cache-v1。在 activite 步骤里,我们需要删除旧的 my-site-cache-v1。

下面的代码会遍历所有的缓存,并删除掉不在 cacheWhitelist 数组(我们定义的缓存白名单)中的缓存。

self.addEventListener('activate', function(event) {var cacheWhitelist = ['pages-cache-v1', 'blog-posts-cache-v1'];
 
  event.waitUntil(caches.keys().then(function(cacheNames) {
      return Promise.all(cacheNames.map(function(cacheName) {if (cacheWhitelist.indexOf(cacheName) === -1) {return caches.delete(cacheName);
          }
        })
      );
    })
  );
});

正文完
 0