service worker 使用
service worker 简介
service worker 的功能和特性可以总结为以下几点:
- service worker 是一个独立 worker 线程,独立于当前网页进程,有自己独立的 worker context
- service worker 的线程能力基于 webworker 而生,通过 postMessage 和 onMessage 进行线程之间的通信;缓存机制是依赖 cache API 实现的。service worker = webworker + cache API
- 一旦被 install 之后,就永远存在,除非被 uninstall;需要的时候可以直接唤醒,不需要的时候自动睡眠
- 可以可编程拦截代理请求( https 请求)和缓存文件,缓存的文件直接可以被网页进程取到(包括网络离线状态)
- 离线内容开发者可控;能向客户端推送消息;不能直接操作 dom
- 必须在 https 环境下才能工作,当然 localhost 或者 127.0.0.1 也是 ok 的
- service worker 是异步的,内部通过 Promise 实现, localStorage 是同步的,因此 service worker 内不许用使用 loaclStorage
- 依赖 HTML5 fetch API 和 Promise
service worker 使用
注册
if ('serviceWorker' in navigator) {
window.addEventListener('load', function () {
navigator.serviceWorker.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);
});
});
}
每次页面加载成功后,就会调用 register() 方法,浏览器将会判断 service worker 线程是否已注册并做出相应的处理。
scope 参数是可选的,默认值为 sw.js
所在的文件目录。
打开 chrome 浏览器, 输入 chrome://inspect/#service-workers 可以可以用 DevTools 查看 Service workers 的工作情况。
安装
service worker 注册后,浏览器就会尝试安装并激活它,并且在这里完成静态资源的缓存。
所以我们在 sw.js
中添加 install 事件
this.addEventListener('install', function (event) {
event.waitUntil(
caches.open('my-test-cache-v1').then(function (cache) {
return cache.addAll([
'/',
'/index.html',
'/main.css',
'/index.js',
'/sw-lifecycle.jpg'
]);
})
);
});
install 事件一般是被用来完成浏览器的离线缓存功能,service worker 的缓存机制是依赖 cache API 实现的。cache API 为绑定在 service worker 上的全局对象,可以用来存储网络响应发来的资源,这些资源只在站点域名内有效,并且一直存在,直到你告诉它不再存储。
缓存和返回请求
每次任何被 service worker 控制的资源被请求到时,都会触发 fetch 事件,因此我们可以利用 fetch 事件对资源响应做一些拦截操作
this.addEventListener('fetch', function (event) {
event.respondWith(
caches.match(event.request).then(function (response) {
// 如果 service worker 有自己的返回,就直接返回,减少一次 http 请求
if (response) {
return response;
}
// 如果 service worker 没有返回,从服务器请求资源
var request = event.request.clone(); // 把原始请求拷过来
return fetch(request).then(function (httpRes) {
// 请求失败了,直接返回失败的结果就好了。。
if (!httpRes && httpRes.status !== 200) {
return response;
}
// 请求成功的话,再一次缓存起来。
var responseClone = httpRes.clone();
caches.open('my-test-cache-v1').then(function (cache) {
cache.put(event.request, responseClone);
});
return httpRes;
});
})
);
});
这样看来,其实可以把 service worker 理解为一个浏览器端的代理服务器,这个代理服务器通过 scope 和 fetch 事件来 hook 站点的请求,来达到资源缓存的功能。
注意:request 和 response 不能直接使用而是通过 clone 的方式使用是因为他们是 stream,因此只能使用一次。
install vs fetch
- install 的优点是第二次访问即可离线,缺点是需要将需要缓存的资源 URL 在编译时插入到脚本中,增加代码量和降低可维护性;
- fetch 的优点是无需更改编译过程,也不会产生额外的流量,缺点是需要多一次访问才能离线可用。
service worker 更新
/sw.js
控制着页面资源和请求的缓存,如果 /sw.js
需要更新应该怎么办呢?
- service worker 控制着整个 App 的离线缓存。 为了避免 service worker 缓存自己导致死锁无法升级,通常将 sw.js 本身的缓存直接交给 HTTP 服务器缓存。
- 更新
sw.js
文件,当浏览器获取到了新的文件,发现sw.js
文件发生更新,就会安装新的文件并触发 install 事件。 - 但是此时已经处于激活状态的旧的 service worker 还在运行,新的 service worker 完成安装后会进入 waiting 状态,直到所有已打开的页面都关闭。
- 新服务工作线程取得控制权后,将会触发其 activate 事件。
// 安装阶段跳过等待,直接进入 activate
self.addEventListener('install', function (event) {
event.waitUntil(self.skipWaiting());
});
self.addEventListener('activate', function (evnet) {
event.waitUntil(
Promise.all([
// 更新客户端
self.clients.claim(),
// 清理旧版本
caches.keys().then(function (cacheList) {
return Promise.all(
cacheList.map(function (cacheName) {
if (cacheName !== 'my-test-cache-v1') {
return caches.delete(cacheName);
}
})
);
})
])
);
});
注意:如果 sw.js
文件被浏览器缓存,则可能导致更新得不到响应。如遇到该问题,可尝试这么做:在 webserver 上添加对该文件的过滤规则,不缓存或设置较短的有效期。
手动更新 /sw.js
也可以借助 Registration.update()
手动更新
var version = '1.0.1';
navigator.serviceWorker.register('/sw.js').then(function (reg) {
if (localStorage.getItem('sw_version') !== version) {
reg.update().then(function () {
localStorage.setItem('sw_version', version)
});
}
});
自动更新
除了浏览器触发更新之外,service worker 还有一个特殊的缓存策略: 如果该文件已 24 小时没有更新,当 Update 触发时会强制更新。这意味着最坏情况下 service worker 会每天更新一次。
调试时更新
可以单独设置调试时 service worker 安装后立即激活:
self.addEventListener('install', function() {
self.skipWaiting();
});
service worker 生命周期
service worker 工作流程
service worker 基于注册、安装、激活等步骤在浏览器 js 主线程中独立分担缓存任务。
- 首先在页面的 javaScript 主线程中使用 navigator.serviceWorker.register() 来注册 servcie worker。
- 如果注册成功,service worker 在 ServiceWorkerGlobalScope 环境中运行; 这是一个特殊的 worker context,与主脚本的运行线程相独立,同时也没有访问 DOM 的能力。
- 后台开始安装步骤,通常在安装的过程中需要缓存一些静态资源。install 事件回调中有两个方法:
- event.waitUntil():传入一个 Promise 为参数,等到该 Promise 为 resolve 状态为止。
- self.skipWaiting():self 是当前 context 的 global 变量,执行该方法表示强制当前处在 waiting 状态的 Service Worker 进入 activate 状态。
- 当 service worker 安装完成后,会接收到一个激活事件(activate event)。激活事件的处理函数中,主要操作是清理旧版本的 service worker 脚本中使用资源。activate 回调中有两个方法:
- event.waitUntil():传入一个 Promise 为参数,等到该 Promise 为 resolve 状态为止。
- self.clients.claim():在 activate 事件回调中执行该方法表示取得页面的控制权, 这样之后打开页面都会使用版本更新的缓存。旧的 Service Worker 脚本不再控制着页面,之后会被停止。
- 激活成功后 service worker 可以控制页面了,刷新页面可以查看 service worker 的工作成果。
service worker 事件
- install: service worker 安装成功后被触发的事件,在事件处理函数中可以添加需要缓存的文件。
- activate:当 service worker 安装完成后并进入激活状态,会触发 activate 事件。通过监听 activate 事件你可以做一些预处理,如对于旧版本的更新、对于无用缓存的清理等。
- message:service worker 通过 postMessage API,可以实现与主线程之间的通信。
下面是一个使用 service worker 的 postMessage API 做的一个简单计算器,其中计算部分在 service worker 线程中完成。假如有一些比较耗时的工作,比如大量计算,或者 fetch 数据,可以将其放入 service worker 线程中,以达到提高页面响应的目的。
- fetch (请求):当浏览器在当前指定的 scope 下发起请求时,会触发 fetch 事件,并得到传有 response 参数的回调函数,回调中就可以做各种代理缓存的事情了。
- push (推送):push 事件是为推送准备的。不过首先需要了解一下 Notification API 和 PUSH API。通过 PUSH API,当订阅了推送服务后,可以使用推送方式唤醒 Service Worker 以响应来自系统消息传递服务的消息,即使用户已经关闭了页面。
示例
这个网站记录了很多 service worker demo。
参考文档
相关文章
- 金融服务领域的大数据:即时分析
- 影响大数据、机器学习和人工智能未来发展的8个因素
- 从0开始构建一个属于你自己的PHP框架
- 如何将Hadoop集成到工作流程中?这6个优秀实践必看
- SEO公司使用大数据优化其模型的5种方法
- 关于Web Workers你需要了解的七件事
- 深入理解HTTPS原理、过程与实践
- 增强分析:数据和分析的未来
- PHP协程实现过程详解
- AI专家:大数据知识图谱——实战经验总结
- 关于PHP的错误机制总结
- 利用数据分析量化协同过滤算法的两大常见难题
- 怎么做大数据工作流调度系统?大厂架构师一语点破!
- 2019大数据处理必备的十大工具,从Linux到架构师必修
- OpenCV中的KMeans算法介绍与应用
- 教大家如果搭建一套phpstorm+wamp+xdebug调试PHP的环境
- CentOS下三种PHP拓展安装方法
- Go语言HTTP Server源码分析
- Go语言HTTP Server源码分析
- 2017年4月编程语言排行榜:Hack首次进入前五十