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. [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 ...

  2. [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 ...

  3. [PWA] 1. Intro to Service worker

    Service worker stays between our browser and noetwork requests. It can help to fetch data from cache ...

  4. Service Worker和HTTP缓存

    很多人,包括我自己,初看Service Worker多一个Cache Storage的时候,就感觉跟HTTP长缓存没什么区别. 例如大家讲的最多的Service Worker能让网页离线使用,但熟悉H ...

  5. Service Worker

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

  6. Service Worker基础知识整理

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

  7. Service Worker MDN英文笔记

    前言: 以前学习基础知识的时候总看别人写的入门文章,但有时候还是一脸懵逼,直到自己用心阅读了MDN的英文文档才对基础知识的一些理论有了更深的理解,所以我在边阅读文档的时候边记录下帮助比较大的,也方便大 ...

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

    许多非REST API甚至可以用于读取数据的POST请求:典型的例子是graphql.soap和其他rpcpapi.但是,Post请求不能在一个现成的渐进式Web应用程序中缓存和脱机使用.浏览器的缓存 ...

  9. JavaScript是如何工作的:Service Worker的生命周期及使用场景

    摘要: 理解Service Worker. 原文:JavaScript 是如何工作的:Service Worker 的生命周期及使用场景 作者:前端小智 Fundebug经授权转载,版权归原作者所有. ...

  10. 转《service worker在移动端H5项目的应用》

    1. PWA和Service Worker的关系 PWA (Progressive Web Apps) 不是一项技术,也不是一个框架,我们可以把她理解为一种模式,一种通过应用一些技术将 Web App ...

随机推荐

  1. 彻底搞懂Spring状态机原理,实现订单与物流解耦

    本文节选自<设计模式就该这样学> 1 状态模式的UML类图 状态模式的UML类图如下图所示. 2 使用状态模式实现登录状态自由切换 当我们在社区阅读文章时,如果觉得文章写得很好,我们就会评 ...

  2. Python学习周总结(一)

    Python-FirstWeek知识汇总 学习了一周python,最大的感触就是要有自己的逻辑思维和发散性思维,考虑事物的广度,层层相扣即使数学逻辑不会,基本的程序功能还是可以实现的,共勉,加油~ 一 ...

  3. Python爬虫中的URLError\HTTPError异常类,异常的抛出

    # _*_ coding : utf-8 _*_# @Time : 2021/11/2 14:20# @Author : 秋泊酱 import urllib.request import urllib ...

  4. SpringBoot数据源相关配置

    数据源配置 单数据源 配置步骤 引入依赖:H2数据库驱动.JDBC依赖.acturator(运维).web模块(用于测试).lambok(使用@Slf4j打印日志). 直接配置所需的Bean,注入容器 ...

  5. python实现模糊操作

    目录: (一)模糊或平滑与滤波的介绍 (二)均值模糊 (1) 原理 (2)代码实现-----均值模糊函数blur() (三)中值模糊------mediaBlur函数 (四)高斯模糊------Gau ...

  6. Java遍历map的五种方式

    使用For-Each迭代entries 这是最常见的方法,并在大多数情况下更可取的.当你在循环中需要使用Map的键和值时,就可以使用这个方法 Map<Integer, Integer> m ...

  7. 多线程01.newThread的方式创建线程

    1.java应用程序的main函数是一个线程,是被jvm启动的时候调用,线程的名字叫main 2.实现一个线程,必须创建一个thread实例,override run方法,并且调用start方法. 3 ...

  8. Atcoder Regular Contest 096 C - Everything on It(组合数学)

    Atcoder 题面传送门 & 洛谷题面传送门 简单题,由于这场 arc 的 F 是 jxd 作业而我不会做,所以只好来把这场的 E 水掉了. 我们记 \(f(i)\) 为钦定 \(i\) 个 ...

  9. cookie的生命周期、访问限制、作用域、prefixes

    cookie的生命周期 cookie的生命周期可以通过两种方式定义: 会话期cookie是最简单的cookie:浏览器关闭后会被自动删除.会话期cookie不需要指定过期时间(Expires)或者有效 ...

  10. ceph简单了解

    ceph简介 ceph是一个统一的分布式存储系统,设计初衷是提供较好的性能.可靠性和可扩展性. 目前已经得到众多云计算厂商的支持并被广泛应用.RedHat及OpenStack都可以与Ceph整合以支持 ...