Service Worker的应用
Service Worker的应用
Service worker本质上充当Web应用程序、浏览器与网络(可用时)之间的代理服务器,这个API旨在创建有效的离线体验,它会拦截网络请求并根据网络是否可用来采取适当的动作、更新来自服务器的的资源,它还提供入口以推送通知和访问后台同步API。
描述
Service Worker本质上也是浏览器缓存资源用的,只不过他不仅仅是Cache,也是通过worker的方式来进一步优化,其基于h5的web worker,所以不会阻碍当前js线程的执行,其最主要的工作原理,1是后台线程,是独立于当前网页线程,2是网络代理,在网页发起请求时代理拦截,来返回缓存的文件。简单来说Service Worker就是一个运行在后台的Worker线程,然后它会长期运行,充当一个服务,很适合那些不需要独立的资源数据或用户互动的功能,最常见用途就是拦截和处理网络请求,以下是一些细碎的描述:
- 基于
web worker(一个独立于JavaScript主线程的独立线程,在里面执行需要消耗大量资源的操作不会堵塞主线程)。 - 在
web worker的基础上增加了离线缓存的能力。 - 本质上充当
Web应用程序(服务器)与浏览器之间的代理服务器(可以拦截全站的请求,并作出相应的动作->由开发者指定的动作)。 - 创建有效的离线体验(将一些不常更新的内容缓存在浏览器,提高访问体验)。
- 由事件驱动的,具有生命周期。
- 可以访问
cache和indexDB。 - 支持推送。
- 可以让开发者自己控制管理缓存的内容以及版本。
Service worker还有一些其他的使用场景,以及service worker的标准能够用来做更多使web平台接近原生应用的事情:
- 后台数据同步。
- 响应来自其它源的资源请求。
- 集中接收计算成本高的数据更新,比如地理位置和陀螺仪信息,这样多个页面就可以利用同一组数据。
- 在客户端进行
CoffeeScript、LESS、CJS/AMD等模块编译和依赖管理(用于开发目的)。 - 后台服务钩子。
- 自定义模板用于特定
URL模式。性能增强,比如预取用户可能需要的资源,比如相册中的后面数张图片。 - 可以配合
App Manifest和Service 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(如XHR和localStorage)不能在service worker中使用。 - 出于安全考量,
Service workers只能由HTTPS承载,localhost本地调试可以使用http。 - 在
Firefox浏览器的用户隐私模式,Service Worker不可用。 - 其生命周期与页面无关(关联页面未关闭时,它也可以退出,没有关联页面时,它也可以启动)。
首先使用Node启动一个基础的web服务器,可以使用anywhere这个包,当然使用其他服务器都是可以的,执行完命令后访问http://localhost:7890/即可。另外写完相关代码后建议重启一下服务,之前我就遇到了无法缓存的问题,包括disk cache和memory 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的解释是因为oninstall和onactivate完成前需要一些时间,service worker标准提供一个waitUntil方法,当oninstall或者onactivate触发时被调用,接受一个promise,在这个promise被成功resolve以前,功能性事件不会分发到service worker。之后便是从caches取出这个CACHE_NAME的key标识的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进行更新,情况就会显得复杂一些,流程如下:首先老的sw为A,新的sw版本为B, B进入install阶段,而A还处于工作状态,所以B进入waiting阶段,只有等到A被terminated后,B才能正常替换A的工作。这个terminated的时机有如下几种方式,1、关闭浏览器一段时间。2、手动清除Service Worker。3、在sw安装时直接跳过waiting阶段。然后就进入了activated阶段,激活sw工作,activated阶段可以做很多有意义的事情,比如更新存储在Cache中的key和value。在下边的代码中,实现了不在白名单的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-worker中clone下来后运行这个示例。
<!-- 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的应用的更多相关文章
- [PWA] 9. Service worker registerion && service work's props, methods and listeners
In some rare cases, you need to ask user to refresh the browsser to update the version. Maybe becaus ...
- [PWA] 2. Service worker life cycle
Once serive worker is registered, the first time we go to the app, we cannot see the logs from servc ...
- [PWA] 1. Intro to Service worker
Service worker stays between our browser and noetwork requests. It can help to fetch data from cache ...
- Service Worker和HTTP缓存
很多人,包括我自己,初看Service Worker多一个Cache Storage的时候,就感觉跟HTTP长缓存没什么区别. 例如大家讲的最多的Service Worker能让网页离线使用,但熟悉H ...
- Service Worker
Service Worker 随着前端快速发展,应用的性能已经变得至关重要,关于这一点大佬做了很多统计.你可以去看看. 如何降低一个页面的网络请求成本从而缩短页面加载资源的时间并降低用户可感知的延时是 ...
- Service Worker基础知识整理
Service Worker是什么 service worker 是独立于当前页面的一段运行在浏览器后台进程里的脚本.它的特性将包括推送消息,背景后台同步, geofencing(地理围栏定位),拦截 ...
- Service Worker MDN英文笔记
前言: 以前学习基础知识的时候总看别人写的入门文章,但有时候还是一脸懵逼,直到自己用心阅读了MDN的英文文档才对基础知识的一些理论有了更深的理解,所以我在边阅读文档的时候边记录下帮助比较大的,也方便大 ...
- Service Worker 离线无法缓存Post请求的问题解决
许多非REST API甚至可以用于读取数据的POST请求:典型的例子是graphql.soap和其他rpcpapi.但是,Post请求不能在一个现成的渐进式Web应用程序中缓存和脱机使用.浏览器的缓存 ...
- JavaScript是如何工作的:Service Worker的生命周期及使用场景
摘要: 理解Service Worker. 原文:JavaScript 是如何工作的:Service Worker 的生命周期及使用场景 作者:前端小智 Fundebug经授权转载,版权归原作者所有. ...
- 转《service worker在移动端H5项目的应用》
1. PWA和Service Worker的关系 PWA (Progressive Web Apps) 不是一项技术,也不是一个框架,我们可以把她理解为一种模式,一种通过应用一些技术将 Web App ...
随机推荐
- CODING添加ssh提示格式错误的问题
不能去.shh文件夹打开id_rsa.pub文件查看 解决方法: 进入.ssh文件夹,然后右键git bash here 输入代码 cat id_rsa.pub 回车即可
- 分布式条件下Integer大小比值的问题
目录 起因 但是,搞大数据的同学请注意了! 动机 验证 处理 起因 临下班,偶然看到阿里巴巴<JAVA开发手册>中,关于整型包装类对象之间值的比较的规约,里面提到强制使用equals,而不 ...
- 使用 FairyGUI 0代码实现游戏界面左右切换
制作原因:项目中需要实现主界面中五个界面左右切换,包含点击切换和滑动切换,并且点击和滑动都需要关联表现,原先项目使用ugui实现该效果,代码量大,容易出问题,不好管控,使用FGUI后不需一行代码即可相 ...
- 用图像识别玩Chrome断网小游戏
先来看一下效果 正文 最近在学习机器学习方面的知识,想着做个东西玩玩,然后就接触到了TensorFlow这个机器学习框架,这个框架封装了机器学习的一些常用算法. 不过要自己实现一套流程还是比较麻烦,我 ...
- 我在组内的Java问题排查分享
前言 最近翻看以前写的 PPT, 发现了在2019年做的一次技术分享,关于 Java 问题排查,由于没什么公司机密可言,整理下分享给大家~ 线上问题处理流程 直接放PPT截图吧,现在看来依然不过时 问 ...
- CF1278F Cards
CF1278F Cards 首先我们知道,一次拿牌的概率是 $ P(i) = \frac 1 m $ ,同时权值是1,所以期望就是 $ \frac{1} m $,拿 $ n $ 次牌贡献是独立的,就是 ...
- base64和图片转换
Base64是一种8Bit字节数据的编码方式. 在参数传输的过程中经常遇到的一种情况:使用全英文的没问题,但一旦涉及到中文就会出现乱码情况.与此类似,网络上传输的字符并不全是可打印的字符,比如二进制文 ...
- R语言与医学统计图形-【25】ggplot图形分面
ggplot2绘图系统--图形分面 ggplot2的分面faceting,主要有三个函数: facet_grid facet_wrap facet_null (不分面) 1. facet_grid函数 ...
- Python基础之赋值与注释
目录 1. 花式赋值 1.1 链式赋值 1.2 交叉赋值 1.3 交叉赋值(解压缩) 2. 注释 2.1 单行注释 2.2 多行注释 1. 花式赋值 1.1 链式赋值 a = 10 b = 10 c ...
- C/C++ Qt 数据库与TableView多组件联动
Qt 数据库组件与TableView组件实现联动,以下案例中实现了,当用户点击并选中TableView组件内的某一行时,我们通过该行中的name字段查询并将查询结果关联到ListView组件内,同时将 ...