service worker 是运行在浏览器后台的 JS 程序,可以操控浏览器端的存储、网络请求、消息推送等。特点:
- 基于 web worker(一个独立于JS主线程的线程,在里面执行耗时操作不会堵塞主线程)
- 在 web worker 的基础上增加了离线缓存的能力;
- 本质上充当服务器与浏览器之间的代理服务器,可以拦截全站的请求并响应;
- 由事件驱动,具有生命周期,包含 register / install / activate 等阶段;
- 只能在 https 域 或者 localhost 下使用;
- 支持降级处理,客户端不支持 service worker 时,对流程没有任何影响;
1. 使用步骤
把 service worker 看作一个代理服务器,通过例子直观的感受它。
1.1 注册 service worker 任务
if ('serviceWorker' in navigator) {
window.addEventListener('load', function () {
navigator.serviceWorker.register('sw.js').then(function (registration) {
console.log('ServiceWorker registration successful with scope: ', registration.scope);
}, function (err) {
console.log('ServiceWorker registration failed: ', err);
});
});
}
1.2 处理安装逻辑
// 监听安装事件
// install 事件一般是被用来设置你的浏览器的离线缓存逻辑
this.addEventListener("install", function (event) {
// event.waitUntil(Promise) 保证缓存动作完成后,才会进入 installed 状态
// 该方法会等待 Promise resolved
event.waitUntil(
// 打开 key 为 pwa-static 的缓存
caches.open("pwa-static").then(function (cache) {
// cache.addAll 指定要缓存的内容,地址为相对于跟域名的访问路径
return cache.addAll(["./index.html"]);
})
);
});
监听 install 事件,当 install 事件触发时,进入 installing 阶段,使用 event.waitUntil() 保证 index.html 被缓存之后才进入 installed 状态。caches.open 用于打开一个缓存,cache.addAll 用于添加需要缓存的文件,参数为url列表。如果 event.waitUntil 接收的 promise 发生错误,则会导致 install 失败。比如添加一个不存在的文件地址。
1.3 监听 fetch 事件
// 注册fetch事件, 拦截请求, 可以拦截所有请求
this.addEventListener('fetch', function(event) {
// 在缓存中匹配对应请求资源直接返回
event.respondWith(caches.match(event.request));
});
在 service worker 中可以监听任何 http 请求,定义响应策略,上面的代码中表示从缓存中读取并响应。
1.4 一个完整的例子
// register service worker
if ('serviceWorker' in navigator) {
navigator.serviceWorker.register('/sw-test/sw.js', { scope: '/sw-test/' }).then(function(reg) {
if(reg.installing) {
console.log('Service worker installing');
} else if(reg.waiting) {
console.log('Service worker installed');
} else if(reg.active) {
console.log('Service worker active');
}
}).catch(function(error) {
// registration failed
console.log('Registration failed with ' + error);
});
}
const version = 'v7';
self.addEventListener('install', function(event) {
event.waitUntil(
caches.open(version).then(function(cache) {
return cache.addAll([
// '/sw-test/',
// '/sw-test/index.html',
'/sw-test/style.css',
'/sw-test/app.js',
'/sw-test/image-list.js',
'/sw-test/star-wars-logo.jpg',
'/sw-test/gallery/bountyHunters.jpg',
'/sw-test/gallery/myLittleVader.jpg',
'/sw-test/gallery/snowTroopers.jpg'
]);
}).then(() => {
return self.skipWaiting()
})
);
});
// 清理过期缓存
function clearCache() {
console.log("clearCacheclearCacheclearCacheclearCache")
return caches.keys().then((keys) => {
return Promise.all(
keys.map((key) => {
console.log(key, version)
if (key !== version) {
return caches.delete(key);
}
})
);
});
}
// 激活事件
self.addEventListener("activate", (event) => {
console.log("activate");
const list = Promise.all([clearCache(), self.clients.claim()])
event.waitUntil(list);
});
self.addEventListener('fetch', function(event) {
event.respondWith(caches.match(event.request).then(function(response) {
if (response !== undefined) {
return response;
} else {
return fetch(event.request).then(function (response) {
let responseClone = response.clone();
return caches.open(version).then(function (cache) {
cache.put(event.request, responseClone);
return response;
})
}).catch(function (err) {
console.log(err, 'err')
return caches.match('/sw-test/gallery/myLittleVader.jpg');
});
}
}));
});
2. API 介绍
2.1 Cache API
service worker 中 self.caches 暴露了访问 cache 的能力,详情见 https://developer.mozilla.org/zh-CN/docs/Web/API/Cache。
// service-worker.js
// self.caches
// self.caches.open(key: string)
// caches.keys()
// caches.delete(key: string);
Cache.match(request, options)
// 返回一个 Promise对象,resolve的结果是跟 Cache 对象匹配的第一个已经缓存的请求,找不到返回 undefined;
Cache.add(request)
// 抓取这个URL, 检索并把返回的 response 对象添加到给定的 Cache 对象。这在功能上等同于调用 fetch(), 然后使用 Cache.put() 将 response 添加到 cache 中;
Cache.addAll(requests)
// 抓取一个URL数组,检索并把返回的response对象添加到给定的Cache对象;
Cache.put(request, response)
// 同时抓取一个请求及其响应,并将其添加到给定的cache。
Cache.delete(request, options)
// 搜索key值为request的Cache 条目。如果找到,则删除该Cache 条目,并且返回一个resolve为true的Promise对象;如果未找到,则返回一个resolve为false的Promise对象。
Cache.keys(request, options)
// 返回一个Promise对象,resolve的结果是Cache对象key值组成的数组。
2.2 event.waitUntil()
查看详细文档,这个方法告诉浏览器事件一直进行,直至 promise 解决,浏览器不应该在事件中的异步操作完成之前终止服务工作线程。
- install 事件使用 waitUntil() 来将服务工作线程保持在 installing 阶段。如果传入 waitUntil() 的 promise 被拒绝,则将此次安装视为失败,丢弃这个服务工作线程。这主要用于确保在服务工作线程安装以前,所有依赖的核心缓存都已经成功载入。
- activate 事件使用 waitUntil() 来延迟函数事件,如 fetch 和 push,直至传入 waitUntil() 的 promise 被解决。这让服务工作线程有时间更新数据库和删除过时缓存,让其他事件能在一个完成更新的状态下进行。
2.3 self.skipWaiting()
当安装成功后,更新的 Worker会处于等待状态,直到现有 Worker 让出授权,另外在刷新期间客户端会重叠。
使用 self.skipWaiting() 会跳过等待,Service Worker 在安装完后立即激活。
3. 注意事项
- 非 PWA 应用,客户端不要缓存 html 文件,否则更会非常的麻烦;
- service worker 文件本身的更新,依赖 HTTP 缓存策略;
- service worker 文件本身,服务端最好设置 cache-control: no-store 不要缓存;
- 多次 register() 同一个 Service Worker 不会触发更新
4. 参考文档
- 使用 Service Workers [ https://developer.mozilla.org/zh-CN/docs/Web/API/Service_Worker_API/Using_Service_Workers#service_workers_demo]
- Service Worker: 简介 [https://developers.google.cn/web/fundamentals/primers/service-workers/]
- MDN Cache [https://developer.mozilla.org/zh-CN/docs/Web/API/Cache]
- MDN fetch [https://developer.mozilla.org/zh-CN/docs/Web/API/Fetch_API/Using_Fetch]
- Service Worker API [https://developer.mozilla.org/zh-CN/docs/Web/API/Service_Worker_API#service_worker_%E7%9A%84%E6%A6%82%E5%BF%B5%E5%92%8C%E7%94%A8%E6%B3%95]
- sw-test [https://github.com/mdn/sw-test]
- PWA 入门: 理解和创建 Service Worker 脚本 [https://zhuanlan.zhihu.com/p/25524382]
- SW 更新 https://harttle.land/2017/04/10/service-worker-update.html#header-0
网友评论