Service Worker 和 PWA 爱恨情仇,制作一个离线应用

2,334 阅读4分钟

Web Worker

浏览器中的 javaScript 都是运行在一个单一主线程上的,在同一时间内只能做一件事情。 Web Worker 是 HTML5 标准的一部分,这一规范定义了一套 API,它允许一段 JavaScript 程序运行在主线程之外的另外一个线程中。

w3c-Web-Worker
点赞等于学会

未来我会继续录制各个大厂以往的笔试真题。

Web WorkerService Worker 有什么关系呢, Web Worker 是临时的,每次做的事情的结果还不能被持久存下来。 Service Worker 在 Web Worker 的基础上加上了持久离线缓存能力。

Service Worker 缓存 和 http 缓存

  • http 缓存

对于 cache-control, Etag, last-modified 等字段我们耳熟能详,这里不过多解释,对于这种缓存还是有缺点的:

  1. 断网的时候,都不能访问
  2. 缓存不能编程,不能精细地对需要缓存的内容增删改查
  • Service Worker
  1. 基于 Web Worker ,一个独立的 worker 线程,独立于当前网页进程;
  2. 让缓存做到优雅和极致

和 PWA 又有什么关系

PWA是为了解决传统Web APP的缺点:

  • 没有桌面入口
  • 无法离线使用
  • 没有Push推送

PWA是前端最火热的一个概念之一,Service Worker为PWA赋能离线可用性以及push消息:

  • 和 CacheStorage 搭配,可以做离线应用
  • 和 Notification 搭配,可以做像 Native APP 那样的消息推送
  • 再加上 Manifest 等,就差不多成了 PWA 了

MDN-CacheStorage-api

Service Worker 怎么用

业务需求:假设我当前页有:

  • 首页 html
  • 一个 get请求
  • 若干 js img 请求
[
	'./',
	'getList',
	'img/avatar_v1.jpg',
	'js/bundle.js',
]

下文将实现对它们的缓存。

简介

Service Worker 可能拥有以下六种状态的一种:

  • 解析成功(parsed)
  • 正在安装(installing)
  • 安装成功(installed)
  • 正在激活(activating)
  • 激活成功(activated)
  • 废弃(redundant)

image

Parsed

首次注册 Service Worker 时,如果满足 Service Worker 运行的条件(在 https / localhost)环境下。 这也是 第一步:

if ('serviceWorker' in navigator) {
	navigator.serviceWorker.register('./sw.js', { scope: './' })
		.then(function (reg) {
			console.log('注册 sw.js 完成');
		})
		.catch(function (err) {
			console.error('注册 sw.js 出错');
		})
}

Installing

Service Worker 脚本解析完成后,浏览器会试着安装,进入下一状态,“installing”

this.addEventListener('install', function (event) {
	console.log('安装 sw.js');
	event.waitUntil(
		caches.open(CACHE_NAME).then(function (cache) {
			return cache.addAll(
				[
					'./',
					'getList',
					'img/avatar_v1.jpg',
					'js/bundle.js',
				]
			);
		})
	)
});

我们通常在该环节缓存文件, event.waitUntil()方法,则 installing 事件会一直等到该方法中的 Promise 完成之后才会成功;若 Promise 被拒,则安装失败,Service Worker 直接进入废弃(redundant)状态。

激活(Activating)

第一次注册并安装成功后,会触发 activate 事件:

addEventListener('activate', event => {
  console.log('安装成功,监听作用域下的所有页面')
})

waiting

上面我们介绍的是第一次注册安装完毕, 但是我们缓存的内容不是一成不变的。不可能永远是

[
	'./',
	'getList',
	'img/avatar_v1.jpg',
	'js/bundle.js',
]

当有sw脚本更新时,在后台默默注册安装新的脚本文件,安装成功后进入 waiting 状态。

image

这个时候有两种选择:

  • 当前所有老版本控制的页面关闭后,再次打开时,新版本的脚本触发 activate 事件
  • 手动调用 this.skipWaiting();

基于这一点。 在 activate 我们会处理前后 sw脚本 需要管理的缓存,这里依据当前 sw.js 的缓存名字,判断实现清除之前的缓存。 为什么要处理:

  • 除非明确地更新缓存,否则缓存将不会被更新;除非删除,否则缓存数据不会过期
  • 浏览器都硬性限制了一个域下缓存数据的大小
this.addEventListener('activate', function (event) {
	console.log('激活 sw.js,可以开始处理 fetch 请求。');
	event.waitUntil(
		caches.keys().then(function (keyList) {
			return Promise.all(keyList.map(function (key) {
				console.log(CACHE_NAME, key)
				if (CACHE_NAME.indexOf(key) === -1) {
					/**
					 * delete() 方法查询request为key的 Cache 条目,如果找到,
					 * 则删除该 Cache 条目
					 */
					return caches.delete(key);
				}
			}))
		})
	)
});

Activated

如果 Service Worker 处于激活态,就可以应对事件性事件 —— fetch。 当然 有必要介绍一下 所有事件如下: 事件:install、activate、message、fetch、push、async。 我们已经学会 三个了。 fetch:处理浏览器发出的请求。

this.addEventListener('fetch', function (event) {
	console.log(event.request);
	event.respondWith(
		caches.match(event.request)
			.then(function (resp) {
				if (resp) {
					console.log(new Date(), 'fetch ', event.request.url, '有缓存,从缓存中取');
					return resp;
				} else {
					console.log(new Date(), 'fetch ', event.request.url, '没有缓存,网络获取');
					return fetch(event.request)
						.then(function (response) {
							return caches.open(CACHE_NAME).then(function (cache) {
								cache.put(event.request, response.clone());
								return response;
							})
						})
				}
			})
	)
});

如果匹配上我们之前的缓存,直接从缓存里面返回, 如果没有的话请求完放到缓存里面。 ** 我们对缓存的操作都是,使用 CacheStorage.open(cacheName) 打开一个Cache 对象,再使用 Cache 对象的方法去处理缓存 **

return caches.open(CACHE_NAME).then(function (cache) {
	cache.put(event.request, response.clone());
	return response;
})

总结

到此,我们已经实现了一个,可以离线访问的页面。 需要掌握的就是:

  • 能够离线访问到内容是靠 CacheStorage 来存储的,

image

  • service-work,提供了一个后台的 worker,随时监听我们发出的请求。

最后

可以查看完整代码:源码

如果喜欢本篇文章,可以关注的微信公众号,如果不嫌烦,还可以把它添加到桌面😀。

参考

lavas-baidu
贝壳技术
The Service Worker Lifecycle Service Worker 生命周期-众成翻译
youngwind/blog
zhuanlan.zhihu
google-doc-service-workers