说明: 本人水平有限, 在翻译过程中难免有理解翻译不准确的地方, 为避免错误引导大家, 希望能够指, 向大家传递正确的观点和知识
正文
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 网页的样子:
也许, 唯一的好处就是你可以点击恐龙来玩免费的游戏 - 但你很快就会觉得无聊
在最近的几年, html5 AppCache 已经能够让 web 应用缓存资源以及离线工作了但是它缺乏足够的灵活性和令人困惑的应用效果证明它并不能很好地解决这个问题 (并且它已经被弃用了)
Service workers 是离线缓存的新标准
哪种缓存是可能的?
在安装期间预缓存资源
在整个应用程序中能够重用的资源, 比如图片 CSSJS 文件, 能够在应用打开的第一时间就被安装
这为 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 在加载页面之前就开始操作了, 并且控制台在加载页面之前将被清除, 所以你讲不能再输出中看到任何日志
来源: https://juejin.im/post/5a7d9b9c5188257a5850dd64