写在前面
- 作者:Flavio Copes
- 原文地址:Service workers: the little heroes behind Progressive Web Apps
- 说明:本人水平有限,在翻译过程中难免有理解翻译不准确的地方,为避免错误引导大家,希望能够指,向大家传递正确的观点和知识。
正文
Service workers是PWA应用的核心。它能够缓存资源以及推送消息,这正是使原生应用与众不同的最大的两个特性。
一个service worker就是你的页面和网络之间的一个可编程代理,它提供了拦截和缓存网络请求的能力。这种能力使得你可以为你的web应用创造一种离线体验。
Service workers是一种特殊的web线程:与web页面相关联的一个JavaScript文件,该文件运行在一个worker上下文中,与主线程分开。这就带来了一种非阻塞的好处-能够使你的运算在不影响UI响应的情况下进行。
因为它是在一个独立线程上的,所以没有DOM访问。它也不能访问本地存储API和XHR API。它只能通过通道消息传递API与主线程通信。
Service workers最近与其它的几个web APIs合作:
- Promises
- Fetch API
- Cache API
它们只能在HTTPS协议的页面上使用(除了本地请求,这些不需要安全连接。使得测试更加简单)。 #### 后台处理 Service workers独立于与之关联的应用程序运行,并且在不活动时可以接收消息。
例如,在以下这些情况下它也可以运行:
- 当您的移动应用程序处于后台时,不活动
- 当您的移动应用程序关闭,甚至不运行在后台
- 当浏览器关闭时,如果应用程序在浏览器中运行
Service workers发挥重要作用的场景:
- 它们可以作为缓存层来处理网络请求,以及在脱机时使用的缓存内容。
- 它们可以推送消息。
一个Service worker只在需要时才运行,当不需要时就会停止。
支持离线
通常,web应用的离线体验是非常差的。没有网络时,移动web应用直接停止工作。另一方面,原生应用能够提供一个可工作版本或者一些非常好的信息。
这不是一个很好的消息,但是这是一个没有网络连接的Chrome网页的样子:
Service workers是离线缓存的新标准。
哪种缓存是可能的?
在安装期间预缓存资源
在整个应用程序中能够重用的资源,比如图片、CSS、JS文件,能够在应用打开的第一时间就被安装。
这为App Shell架构提供了基础。
缓存网络请求
使用Fetch API,我们能够编辑来自服务的响应,确定服务器是否可访问,并从缓存中提供响应。
Service Worker的生命周期
一个Service Worker经过三步完成一个完整的功能:
- 注册
- 安装
- 激活
注册
注册就是告诉浏览器这个Service Worker在什么地方以及在后台运行安装。
比如,在worker.js
中注册一个Service Worker:
if ('serviceWorker' in navigator) {
window.addEventListener('load', () => {
navigator.serviceWorker.register('/worker.js')
.then((registration) => {
console.log('Service Worker registration completed with scope: ', registration.scope)
}, (err) => {
console.log('Service Worker registration failed', err)
})
})
} else {
console.log('Service Workers not supported')
}
复制代码
即使代码被调用多次或者已经被更新了,那么如果这个Service Worker是新的,而且之前没有被注册过,浏览器将只执行注册。
范围
register()
函数也将接受一个范围参数,这个参数是个路径决定了应用程序的哪一部分可以由Service Worker控制。
它默认包含在包Service Worker文件的文件夹中的所有文件和子文件夹,所以,如果你把它放到根文件中,它将控制整个应用。在子文件中,它将只控制这个路径下的页面。
下面的示例通过/notifications/
文件夹范围来注册worker。
navigator.serviceWorker.register('/worker.js', {
scope: '/notifications/'
})
复制代码
这个/
是非常重要的:在这种情况下,页面/notifications
将不会触发Service Worker,然而如果这个范围是:
{ scope: '/notifications' }
复制代码
它就会奏效。
注意:Service Worker不能在一个文件中向外提升自己:如果它的文件放在/notifications
下,它将不能控制/
路径或者其它任何不在/notifications
文件内的路径。 #### 安装 如果浏览器确定Service Worker已经过时或从未注册过,它将去安装它:
self.addEventListener('install', (event) => {
//...
});
复制代码
这是一个很好的时机,可以让Service Worker通过初始化缓存来使用。然后使用缓存API缓存应用程序Shell和静态资源。
激活
一旦Service Worker被成功的注册或安装,第三步就是激活。
在这时候,Service Worker将会随着新的页面加载而开始工作。
它不能与已经加载的页面交互,所以Service Worker只有在用户第二次与应用程序交互或重新加载已经打开的页面时才有用。
self.addEventListener('activate', (event) => {
//...
});
复制代码
这个事件的一个很好的用例是清理旧的缓存和与旧版本相关的旧版本,这些旧版本在新版本的Service Worker中没有使用。
更新一个Service Worker
要更新一个Service Worker,你只需要改变其中一个字节。当注册的代码运行的时候,它将被更新。
只要一个Service Worker被更新了,直到所有加载了旧Service Worker的页面都被关闭,它才会出现。
这确保了不会在应用或者页面已经运行的进程上出现任何问题。
刷新页面是不够的,旧的worker仍然在运行,它还没有被移除。
请求事件
当一个资源在网络上被请求时就触发了一个请求事件。
这就让我们在发起网络请求时能够查看缓存。
例如,下面的代码片段使用缓存API来检查所请求的URL是否已经存储在缓存的响应中。如果已经缓存,就会返回缓存的响应。反之,它执行获取请求并返回它。
self.addEventListener('fetch', (event) => {
event.respondWith(
caches.match(event.request)
.then((response) => {
if (response) {
//entry found in cache
return response
}
return fetch(event.request)
}
)
)
})
复制代码
后台同步
后台同步允许将传出的连接延迟到用户拥有一个工作网络连接。
这是确保用户能够使用离线应用的关键,当连接打开时,队列服务器端更新(而不是展示一个无休止的spinning wheel来试图得到一个信号)。
navigator.serviceWorker.ready.then((swRegistration) => {
return swRegistration.sync.register('event1')
});
复制代码
下面的代码在service worker中监听事件。
self.addEventListener('sync', (event) => {
if (event.tag == 'event1') {
event.waitUntil(doSomething())
}
})
复制代码
doSomething()
返回一个promise。如果失败了,另一个同步事件将被安排自动重试,直到成功。
这也允许应用程序在有可用连接的情况下尽快更新服务器上的数据。
推送事件
在service worker允许web应用程序向用户提供本机推送通知。
Push和Notifications是两种不同的概念和技术,组合应用来实现我们所熟知的消息推送。Push提供了允许服务器向 service worker发送信息的机制,而Notifications是service worker向用户显示信息的方式。
因为service workers在应用停止的时候仍可以运行,它们可以监听推送事件。接着向用户显示信息,并更新应用状态。
推送事件是由一个后端发起,并通过浏览器推送服务。
下面是一个web worker如何侦听传入推送事件的示例:
self.addEventListener('push', (event) => {
console.log('Received a push event', event)
const options = {
title: 'I got a message for you!',
body: 'Here is the body of the message',
icon: '/img/icon-192x192.png',
tag: 'tag-for-this-notification',
}
event.waitUntil(
self.registration.showNotification(title, options)
)
})
复制代码
关于控制台日志的说明:
如果你在service worker中有任何控制台消息,确保您打开了由Chrome Devtools提供的保存日志功能。
另外,因为service worker在加载页面之前就开始操作了,并且控制台在加载页面之前将被清除,所以你讲不能再输出中看到任何日志。