PWA 渐进式网络应用 - 2 - 使用 Service Workers

本文介绍了ServiceWorkers在PWA中的作用及其如何通过拦截网络请求来改善用户体验,包括离线访问和资源缓存策略。探讨了ServiceWorkers的工作原理、生命周期、版本控制及与主JavaScript线程的交互方式。

PWA 渐进式网络应用 - 2 - 使用 Service Workers

1. 简介

释放 PWA 力量的关键在于 Service WorkersService Workers 可以在浏览器后台挂起新线程,并且可以拦截网络请求进行本地缓存或请求转发,相当于充当 Web 应用程序与浏览器间的代理服务器,也可以在网络可用时,作为浏览器和网络间的代理

Service Workers 旨在使得能够创建有效的离线体验,拦截网络请求并给予网络是否可用以及更新的资源是否驻留在服务器上来采取适当的动作。还允许访问推送通知和后台同步 API

Service Workers 是一个注册在指定源和路径下的事件驱动 worker 它采用 javascript 控制关联的页面或者网站,拦截并修改访问和资源请求,细粒度的缓存资源,可以通过它完全控制应用在特定情形下的表现

// 判断浏览器是否支持 service workers 支持就添加事件监听器
if ('serviceWorker' in navigator) {
    window.addEventListener('load', function() {
        navigator.serviceWorker
            // 注册 service worker
            .register('/sw.js', { scope: '/' })
            .then(function(registration) {
                // 注册成功
                console.log(
                    'ServiceWorker registration successful with scope: ',
                    registration.scope
                );
            })
            .catch(function(err) {
                // 注册失败:(
                console.log('ServiceWorker registration failed: ', err);
            });
    });
}

注册时的 scope 参数是可选的,用于指定 service workers 控制哪个路径下的内容,例如 指定 {scope: '/admin'} 那么 这个 service workers 只能捕获 path 为 ‘/admin’ 开头的 fetch 事件

2. 注意事项

  • Service Workers 运行在 worker 上下文,因此不能访问 DOM
  • 一旦被 install 就永远存在,除非被 uninstall
  • 可以向客户端推送消息
  • 相对于驱动应用的主 javascript 线程,它运行在其他线程中,不会造成堵塞
  • 设计上完全异步,同步 API (XHR、localStorage) 不能在其中使用
  • 安全上考虑,由于具有修改网络请求的能力,所以只能由 HTTPS 承载

3. Service Workers 缓存

由于 Service Workers 可以拦截网络请求,由此可以替代服务器告诉浏览器资源需要缓存多久,作为开发者可以全权掌握

这里使用了 CacheStorage 可以更方便的将请求和请求结果缓存其他,但是它

  • 只能缓存 get 请求
    *
const cacheName = 'text';
// 进入 service worker 安装事件
self.addEventListener('install', function(event) {
    console.log('herr');
    event.waitUntil(
        caches
            .open(cacheName) // 打开缓存
            .then(cache =>
                cache.addAll([
                    '/text.json', // 添加文件到缓存中
                    '/about.html'
                ])
            )
            .then(() => console.log('suce'))
    );
});
// 监听 fetch 事件
self.addEventListener('fetch', function(event) {
    console.log(caches);
    event.respondWith(
        caches
            .match(event.request) // 检查传入请求的 url 是否在缓存中有与之匹配的项
            .then(res => res || fetch(event.request)) // 有的话就将其返回,否则通过正常网络请求获取
    );
});
api 解析
  1. event.waitUntil() 方法,用来确保 service worker 不会再 waitUntil() 里边的代码执行完毕之前安装完成

  2. 可以使用 self.skipWaiting() 方法,手动触发 Service Worker activate 事件,并告知 Service Worker 立即开始工作,而无需等待用户跳转或刷新页面。

  3. caches.open(cacheName) 方法用来创建一个名为 cacheName 的缓存,成功后返回 promise

  4. cache.addAll([]) 方法用来指定缓存的资源列表

注意:以上方法,并不会在用户第一次访问网站的时候激活 Service Worker 来控制页面,只有当 Service Worker 安装完成后用户刷新页面或者用户跳转至网站其他页面,才会被激活并且拦截请求

self.addEventListener('install', function(event) {
    // 触发 activate 事件,告知 Service Worker 立即开始工作
    event.waitUntil(self.skipWaiting());
});

skipWaiting() 函数强制等待中的 Service Worker 被激活,如果配合 self.clients.claim() 一起使用,可以确保底层 Service Worker 的更新立即生效

const SW_VERSION = '1.0.1';
self.addEventListener('install', function(event) {
    // 触发 activate 事件,告知 Service Worker 立即开始工作
    event.waitUntil(self.skipWaiting());
});
self.addEventListener('activate', function(event) {
    // 确保更新立即生效
    event.waitUntil(self.clients.claim());
});

4. Service Workers 版本控制

如果你更改了 WEB 文件,并想要确保用户接受到最新版本的文件,这个时候需要给 Service Workers 一个更新时间点

每当对 Service Workers 文件本身做出任何更改时,都会自动触发 Service Workers 的更新流程,每当用户导航至你的网站时,浏览器会尝试在后台重新下载 Service Workers 即使下载的 Service Workers 文件与当前的相比只有一个字节的差别,浏览器也会认为他是新的

注意:’sw.js’ 更新后,会触发更新算法并触发 install 事件,但是此时已经处于激活状态的旧的 Service Worker 还在运行, 新的 Service Worker 安装完成后,会进入 waiting 状态,直到所有以打开页面都关闭,旧的 Service Worker自动停止,新的 Service Worker 会在重开的页面生效

1. 自动更新

如果需要在每次更改 Service Worker 文件后,马上更新所有资源,则可以在 install 事件中执行 self.skipWaiting() 方法跳过 waiting 状态,然后会直接进入 activate 阶段。接着在 activate 事件发生时,通过执行 self.clients.claim() 方法,更新所有客户端上的 Service Worker,参考下边例子

// 每次版本更新是在这里做记录
const SW_VERSION = '1.0.6';

self.addEventListener('install', function(event) {
    // 触发 activate 事件,告知 Service Worker 立即开始工作
    event.waitUntil(self.skipWaiting());
});

// 监听 service workers 更新时间
self.addEventListener('activate', function(event) {
    console.log('sw.js 更新');
    event.waitUntil(
        Promise.all([
            // 更新客户端
            self.clients.claim(),
            // 清理旧版本
            caches.keys().then(cacheNames => {
                return Promise.all(
                    cacheNames.map(cacheName => {
                        console.log(cacheName, SW_VERSION);
                        // 凡是缓存名不是当前版本号的缓存全部删除,这样最终只会保留当前版本号对应的文件
                        if (cacheName !== SW_VERSION) {
                            return caches.delete(cacheName);
                        }
                    })
                );
            })
        ])
    );
});

// 缓存请求
self.addEventListener('fetch', function(event) {
    event.respondWith(
        caches.match(event.request).then(res => {
            // 检查缓存中有就直接返回缓存
            if (res) {
                return res;
            }
            // 请求是一个流,只能使用一次,为了再次使用这里需要克隆
            const requestToCache = event.request.clone();

            // 针对缓存中没有存在资源这里重新请求一下
            return fetch(requestToCache).then(res => {
                // 请求返回的结果错误 则不缓存
                if (!res || res.status !== 200) {
                    return res;
                }
                // 克隆响应
                const responseToCache = res.clone();

                // 打开缓存,将响应添加进去
                caches
                    .open(SW_VERSION)
                    .then(cache => cache.put(requestToCache, responseToCache));

                return res;
            });
        })
    );
});
2. 手动更新 registration.update()
if ('serviceWorker' in navigator) {
    navigator.serviceWorker.register('/sw.js').then(registration => {
        console.log('successful', registration);
        document.getElementById('btn').addEventListener('click', function() {
            registration.update().then(() => console.log('update'));
        });
    });
}
3. 24 小时强制更新

Service Worker 处除了由浏览器触发更新之外,还应用了特殊的缓存策略: 如果该文件已 24 小时没有更新,当 Update 触发时会强制更新。这意味着最坏情况下 Service Worker 会每天更新一次。

5. 生命周期

  1. installing 安装,会触发 install 事件,指定一些静态资源进行离线缓存
  2. installed 安装后,完成安装,等待其他 Service Worker 线程被关闭。
  3. activating 激活,在这个状态下没有被其他的 Service Worker 控制的客户端,允许当前的 worker 完成安装,并且清除了其他的 worker 以及关联缓存的旧缓存资源,等待新的 Service Worker 线程被激活。
  4. activated 激活后,可以处理功能性的事件 fetch、push、sync
  5. redundant 废弃状态
对应生命周期支持的事件
  1. install Service Worker 安装成功后被触发的事件,事件回调中处理缓存,有两个方法可以使用

    • event.waitUntil() 需要传入一个 promise 为参数,表示等到该 Promise 为 resolve 状态,再变更状态为完成安装
    • self.skipWaiting() 执行该方法表示,强制当前处在 waiting 状态的 Service Worker 进入 activate 状态
  2. activate 安装完成后,进入激活状态时触发,这里可以做缓存清理或者版本更新,回调中有两个方法可以使用

    • event.waitUntil() 与之前的接口一样都是等到 参数 promise 为 resolve 状态,再变更状态为完成激活
    • self.clients.claim() 取得页面控制权,这样之后打开页面都会使用版本更新的缓存。旧的 Service Worker 脚本不再控制着页面,之后会被停止。
  3. message Service Worker 运行于独立 context 中,无法直接访问当前页面主线程的 DOM 等信息,但是通过 postMessage API,可以实现他们之间的消息传递,这样主线程就可以接受 Service Worker 的指令操作 DOM

  4. fetch 请求,当浏览器在指定的 scope 下发起请求时,会触发,在回调中可以处理代理缓存

  5. push 推送,通过 PUSH API ,当订阅了推送服务后,可以使用推送的方式唤醒 Service Worker 以响应来自系统消息,即使用户已经关闭了页面

相关文章

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值