Service Worker的应用

Service worker本质上充当Web应用程序、浏览器与网络(可用时)之间的代理服务器,这个API旨在创建有效的离线体验,它会拦截网络请求并根据网络是否可用来采取适当的动作、更新来自服务器的的资源,它还提供入口以推送通知和访问后台同步API

描述

Service Worker本质上也是浏览器缓存资源用的,只不过他不仅仅是Cache,也是通过worker的方式来进一步优化,其基于h5web worker,所以不会阻碍当前js线程的执行,其最主要的工作原理,1是后台线程,是独立于当前网页线程,2是网络代理,在网页发起请求时代理拦截,来返回缓存的文件。简单来说Service Worker就是一个运行在后台的Worker线程,然后它会长期运行,充当一个服务,很适合那些不需要独立的资源数据或用户互动的功能,最常见用途就是拦截和处理网络请求,以下是一些细碎的描述:

  • 基于web worker(一个独立于JavaScript主线程的独立线程,在里面执行需要消耗大量资源的操作不会堵塞主线程)。
  • web worker的基础上增加了离线缓存的能力。
  • 本质上充当Web应用程序(服务器)与浏览器之间的代理服务器(可以拦截全站的请求,并作出相应的动作->由开发者指定的动作)。
  • 创建有效的离线体验(将一些不常更新的内容缓存在浏览器,提高访问体验)。
  • 由事件驱动的,具有生命周期。
  • 可以访问cacheindexDB
  • 支持推送。
  • 可以让开发者自己控制管理缓存的内容以及版本。

Service worker还有一些其他的使用场景,以及service worker的标准能够用来做更多使web平台接近原生应用的事情:

  • 后台数据同步。
  • 响应来自其它源的资源请求。
  • 集中接收计算成本高的数据更新,比如地理位置和陀螺仪信息,这样多个页面就可以利用同一组数据。
  • 在客户端进行CoffeeScriptLESSCJS/AMD等模块编译和依赖管理(用于开发目的)。
  • 后台服务钩子。
  • 自定义模板用于特定URL模式。性能增强,比如预取用户可能需要的资源,比如相册中的后面数张图片。
  • 可以配合App ManifestService Worker来实现PWA的安装和离线等功能。
  • 后台同步,启动一个service worker即使没有用户访问特定站点,也可以更新缓存。
  • 响应推送,启动一个service worker向用户发送一条信息通知新的内容可用。
  • 对时间或日期作出响应。
  • 进入地理围栏(LBS的一种应用)。

示例

实现一个简单的Service worker应用示例,这个示例可以在断网的时候同样可以使用,相关的代码在https://github.com/WindrunnerMax/webpack-simple-environment/tree/simple--service-worker,在这里就是用原生的Service Worker写一个简单示例,直接写原生的Service Worker比较繁琐和复杂,所以可以借助一些库例如Workbox等,在使用Service Worker之前有一些注意事项:

  • Service worker运行在worker上,也就表明其不能访问DOM
  • 其设计为完全异步,同步API(如XHRlocalStorage)不能在service worker中使用。
  • 出于安全考量,Service workers只能由HTTPS承载,localhost本地调试可以使用http
  • Firefox浏览器的用户隐私模式,Service Worker不可用。
  • 其生命周期与页面无关(关联页面未关闭时,它也可以退出,没有关联页面时,它也可以启动)。

首先使用Node启动一个基础的web服务器,可以使用anywhere这个包,当然使用其他服务器都是可以的,执行完命令后访问http://localhost:7890/即可。另外写完相关代码后建议重启一下服务,之前我就遇到了无法缓存的问题,包括disk cachememory cache,要重启服务才解决。还有要打开的链接为localhost,自动打开浏览器可能并不是localhost所以需要注意一下。如果要清理缓存的话,可以在浏览器控制台的Application项目中Storage点击Clear site data就能清理在网站中的所有缓存了。如果使用express或者koa等服务器环境,还可以尝试使用Service Worker来缓存数据请求,同样提供数据请求的path即可。

$ npm install -g anywhere
$ anywhere 7890 # http://localhost:7890/

编写一个index.html文件和sw.js文件,以及引入相关的资源文件,目录结构如下,可以参考https://github.com/WindrunnerMax/webpack-simple-environment/tree/simple--service-worker,当然直接clone下来运行一个静态文件服务器就可以直接使用了。

simple--service-worker
├── static
│   ├── avatar.png
│   └── cache.js
├── index.html
└── sw.js

html中引入相关文件即可,主要是为了借助浏览器环境,而关注的位置是js

<!DOCTYPE html>
<html>
<head><meta charset="utf-8"><title>Service Worker</title><style type="text/css">.avatar{width: 50px;height: 50px;border-radius: 50px;}</style>
</head>
<body><img class="avatar" src="./static/avatar.png"><script type="text/javascript">navigator.serviceWorker.register("sw.js").then(() => {console.info("注册成功");}).catch(() => {console.error("注册失败");});</script><script src="./static/cache.js"></script>
</body>
</html>

使用Service worker的第一步,就是告诉浏览器,需要注册一个Service worker脚本,在这里我们直接将其写到了index.html文件中了。默认情况下,Service worker只对根目录/生效,如果要改变生效范围可以在register时加入第二个参数{ scope: "/xxx"},也可以直接在注册的时候就指定路径/xxx/sw.js

navigator.serviceWorker
.register("sw.js")
.then(() => {console.info("注册成功")
}).catch(err => {console.error("注册失败")
})

一旦登记成功,接下来都是Service worker脚本的工作,下面的代码都是写在service worker脚本里面的,登记后,就会触发install事件,service worker脚本需要监听这个事件。首先定义这个cache的名字,相当于是标识这一个缓存对象的键值,之后的urlsToCache数组是即将要缓存的数据,只要给定了相关的path,连数据请求也是同样能够缓存的,而不仅仅是资源文件,当然这边必须是Get的请求下使用,这是Cache这个API决定的。之后便是进行install,关于event.waitUntil可以理解为new Promise的作用,是要等待serviceWorker运行起来才继续后边的代码,其接受的实际参数只能是一个Promise。在MDN的解释是因为oninstallonactivate完成前需要一些时间,service worker标准提供一个waitUntil方法,当oninstall或者onactivate触发时被调用,接受一个promise,在这个promise被成功resolve以前,功能性事件不会分发到service worker。之后便是从caches取出这个CACHE_NAMEkey标识的cache,之后使用cache.addAll将数组中的path告诉cache,在第一次打开的时候,Service worker会自动去请求相关的数据并且缓存起来,使用Service worker去请求的数据,在Chrome控制台的Network中会显示一个小小的齿轮图标,很好辨认。

const CACHE_NAME = "service-worker-demo";
const urlsToCache = ["/", "/static/avatar.png", "/static/cache.js"];this.addEventListener("install", event => {event.waitUntil(caches.open(CACHE_NAME).then(cache => {console.log("[Service Worker]", urlsToCache);return cache.addAll(urlsToCache);}));
});

之后是activated阶段,如果是第一次加载sw,在安装后,会直接进入activated阶段,而如果sw进行更新,情况就会显得复杂一些,流程如下:首先老的swA,新的sw版本为B, B进入install阶段,而A还处于工作状态,所以B进入waiting阶段,只有等到Aterminated后,B才能正常替换A的工作。这个terminated的时机有如下几种方式,1、关闭浏览器一段时间。2、手动清除Service Worker3、在sw安装时直接跳过waiting阶段。然后就进入了activated阶段,激活sw工作,activated阶段可以做很多有意义的事情,比如更新存储在Cache中的keyvalue。在下边的代码中,实现了不在白名单的CACHE_NAME就清理,可以在这里实现一个version也就是版本的控制,之前的版本就要清理等,另外还查看了一下目前的相关缓存。

this.addEventListener("activate", event => {// 不在白名单的`CACHE_NAME`就清理const cacheWhitelist = ["service-worker-demo"];event.waitUntil(caches.keys().then(cacheNames => {return Promise.all(cacheNames.map(cacheName => {if (cacheWhitelist.indexOf(cacheName) === -1) {return caches.delete(cacheName);}}));}));// 查看一下缓存event.waitUntil(caches.open(CACHE_NAME).then(cache => cache.keys().then(res => console.log(res))));
});

之后便是拦截请求的阶段了,该阶段是sw关键的一个阶段,用于拦截代理所有指定的请求,并进行对应的操作,所有的缓存部分,都是在该阶段。首先我们直接拦截掉所有的请求,在最前边的判断操作是为了防止所有的请求都被拦截从而都在worker里边发起请求,当然不进行判断也是可以使用的。然后对于请求如果匹配到了缓存,那么就直接从缓存中取得数据,否则就使用fetch去请求新的。另外如果有需要的话我们不需要在事件响应时进行匹配 可以直接将所有发起过的请求缓存。

this.addEventListener("fetch", event => {const url = new URL(event.request.url);if (url.origin === location.origin && urlsToCache.indexOf(url.pathname) > -1) {event.respondWith(caches.match(event.request).then(resp => {if (resp) {console.log("fetch ", event.request.url, "有缓存,从缓存中取");return resp;} else {console.log("fetch ", event.request.url, "没有缓存,网络获取");return fetch(event.request);// // 如果有需要的话我们不需要在事件响应时进行匹配 可以直接将所有发起过的请求缓存// return fetch(event.request).then(response => {//     return caches.open(CACHE_NAME).then(cache => {//         cache.put(event.request, response.clone());//         return response;//     });// });}}));}
});

第一次打开时控制台的输出:

cache.js loaded
[Service Worker] (3) ['/', '/static/avatar.png', '/static/cache.js']
注册成功
(3) [Request, Request, Request]

第二次及之后打开的控制台输出:

fetch  http://localhost:7811/static/avatar.png 有缓存,从缓存中取
fetch  http://localhost:7811/static/cache.js 有缓存,从缓存中取
注册成功
cache.js loaded

至此我们就完成了一个简单的示例,在第二次打开页面的时候,我们可以将浏览器的网络连接断开,例如关闭文件服务器或者在控制台的Network中选择Offline,而我们也可以看到页面依旧正常加载,不需要网络服务,另外也可以在Network的相关的数据的Size列会出现(ServiceWorker)这个信息,说明资源是从ServiceWorker加载的缓存数据。可以在https://github.com/WindrunnerMax/webpack-simple-environment/tree/simple--service-workerclone下来后运行这个示例。

<!-- index.html -->
<!DOCTYPE html>
<html>
<head><meta charset="utf-8"><title>Service Worker</title><style type="text/css">.avatar{width: 50px;height: 50px;border-radius: 50px;}</style>
</head>
<body><img class="avatar" src="./static/avatar.png"><script type="text/javascript">navigator.serviceWorker.register("sw.js").then(() => {console.info("注册成功");}).catch(() => {console.error("注册失败");});</script><script src="./static/cache.js"></script>
</body>
</html>
// sw.js
const CACHE_NAME = "service-worker-demo";
const urlsToCache = ["/", "/static/avatar.png", "/static/cache.js"];this.addEventListener("install", event => {event.waitUntil(caches.open(CACHE_NAME).then(cache => {console.log("[Service Worker]", urlsToCache);return cache.addAll(urlsToCache);}));
});this.addEventListener("activate", event => {// 不在白名单的`CACHE_NAME`就清理const cacheWhitelist = ["service-worker-demo"];event.waitUntil(caches.keys().then(cacheNames => {return Promise.all(cacheNames.map(cacheName => {if (cacheWhitelist.indexOf(cacheName) === -1) {return caches.delete(cacheName);}}));}));// 查看一下缓存event.waitUntil(caches.open(CACHE_NAME).then(cache => cache.keys().then(res => console.log(res))));
});this.addEventListener("fetch", event => {const url = new URL(event.request.url);if (url.origin === location.origin && urlsToCache.indexOf(url.pathname) > -1) {event.respondWith(caches.match(event.request).then(resp => {if (resp) {console.log("fetch ", event.request.url, "有缓存,从缓存中取");return resp;} else {console.log("fetch ", event.request.url, "没有缓存,网络获取");return fetch(event.request);// // 如果有需要的话我们不需要在事件响应时进行匹配 可以直接将所有发起过的请求缓存// return fetch(event.request).then(response => {//     return caches.open(CACHE_NAME).then(cache => {//         cache.put(event.request, response.clone());//         return response;//     });// });}}));}
});
// cache.js
console.log("cache.js loaded");
// avatar.png
// [byte]png

每日一题

https://github.com/WindrunnerMax/EveryDay

参考

https://github.com/mdn/sw-test/
https://zhuanlan.zhihu.com/p/25459319
https://zhuanlan.zhihu.com/p/115243059
https://zhuanlan.zhihu.com/p/161204142
https://github.com/youngwind/service-worker-demo
https://mp.weixin.qq.com/s/3Ep5pJULvP7WHJvVJNDV-g
https://developer.mozilla.org/zh-CN/docs/Web/API/Cache
https://developer.mozilla.org/zh-CN/docs/Web/API/Service_Worker_API
https://www.bookstack.cn/read/webapi-tutorial/docs-service-worker.md

Service Worker的应用相关推荐

  1. Service Worker

    Service Worker 随着前端快速发展,应用的性能已经变得至关重要,关于这一点大佬做了很多统计.你可以去看看. 如何降低一个页面的网络请求成本从而缩短页面加载资源的时间并降低用户可感知的延时是 ...

  2. 初识Service Worker

    当下PWA比较火,而Service Worker是实现PWA的一项关键技术,今天我们一起了解下关于Service Worker的一些基础知识和适用场景. 什么是Server Worker 我们先来看一 ...

  3. Service Worker 图片加载失败处理

    Service Worker 图片加载失败处理 参考文档 git clone https://gitee.com/wjj0720/Service-Worker.git 运行 npm i npm sta ...

  4. Service Worker 离线无法缓存Post请求的问题解决

    Service Worker 离线无法缓存Post请求的问题解决 参考文章: (1)Service Worker 离线无法缓存Post请求的问题解决 (2)https://www.cnblogs.co ...

  5. [PWA] Show Notifications when a Service Worker is Installed or Updated

    Service Workers get installed and activated in the background, but until we reload the page they don ...

  6. Web API之service worker

    一.参考链接 https://developer.mozilla.org/en-US/docs/Web/API/Service_Worker_API http://www.alloyteam.com/ ...

  7. Service Worker ——这应该是一个挺全面的整理

    我在之前有空的时候粗略学习了一下 Service Worker :最近有空,所以花时间再去学习了下.我在这里整理了下,希望对大家有帮助. 同时,如果文章中有错误或者描述不当的地方,欢迎大家能够帮我指正 ...

  8. Service Worker的基本使用

    环境准备 安装http-server npm install -g http-server 准备index.html <head>   <title>Minimal PWA&l ...

  9. service worker之cache实践--sw-precache

    Progressive web application是谷歌推出的一种渐进式应用,我觉得其实PWA是一种非常具有发展前景的技术.首先,PWA是由谷歌推出的,而且跨平台,PWA可以给你类似于原生APP的 ...

  10. Service Worker 概念简介

    原文 丰富的离线体验.定期后台同步.推送通知--通常需要原生应用程序的功能--正在网络上出现. Service Worker 提供了所有这些功能所依赖的技术基础. What is a service ...

最新文章

  1. 短短6小时,AI设计出40000种毒气分子,很多毒性远超战用神经毒剂
  2. mysql损坏表修复
  3. 推荐15个让新手爱不释手的Python高级库
  4. 西贝莜面村员工手册_西贝那达慕草原美食节 引领文化生活新消费
  5. python登录验证程序_Python模拟用户登录验证
  6. 简单构建一个xmlhttp对象池合理创建和使用xmlhttp对象
  7. App 常用图标尺寸规范汇总
  8. CAD2006提示没有足够的权限来安装此产品
  9. mysql 右连接(right join)
  10. html文件怎么可以查错,CSS_CSS 网页布局中易犯的10个小错误小结,1. 检查HTML元素是否有拼写错误 - phpStudy...
  11. 电子电路:电流镜电路
  12. python定位元素_Python元素定位
  13. 天长地久 (20分)
  14. 常用电源管理稳压IC
  15. 读取和讯博客数据可视化分析
  16. 中华英才网 java_中华英才网校园招聘
  17. [转载]使用 SVK 构建分布式版本控制环境
  18. 计算机网络期末复习整理
  19. saltstack+git+rsync发布代码
  20. php7序列化,PHP内核层解析反序列化漏洞

热门文章

  1. 内存管理单元MMU简介
  2. 教你如何做出自己想要的PHP Docker镜像
  3. 像聊天机器人一样的c语言程序,示例:聊天机器人
  4. MySQL面试题1:MySQL架构体系相关
  5. 消息中间件常见问题汇总
  6. Java限流之 —— Guawa
  7. Redis的安装与常用配置说明
  8. 4.2WebHost配置「深入浅出ASP.NET Core系列」
  9. 20172303 2018-2019-1《程序设计与数据结构》第7周学习总结
  10. 【在线集成开发环境】Eclipse Che简单上手体验