本文涉及几个知识点:fetch、caches、indexDB 等都不会详细介绍,仅对于其中某些点带过javascript
serviceWorker,服务工做线程,顾名思义,只是做为工做线程存在,不掺和到JS主线程中来,介于 浏览器 & 服务器中间层,可拦截指定 client 所发起的全部请求html
目前 PWA(Progress Web App) 的概念很火,大体就是让 web 也跟 app 同样,能够实现添加到桌面、消息推送、离线使用等功能,如 饿了么 在三月份左右就在H5上整了个 PWA 的页面。而其中的关键点,其实就是离线使用的功能,也就是 sw 在其中的做用。因为 sw 能够拦截 client 的请求,也就是可以根据请求,把请求后的 response 用浏览器缓存 caches 缓存下来,以实现离线的使用java
说到 sw 的生命周期,就得祭奠出这张图了web
步骤分为如下部分:chrome
- register 这个是由 client 端发起,注册一个 serviceWorker,这须要一个专门的 sw 处理文件
- install 注册成功后,此时 sw 中会触发 install 事件, 需知 sw 中都是事件触发的方式进行的逻辑调用
- activate 安装后要等待激活,也就是 activated 事件,只要 register 成功后就会触发 install ,但不会当即触发 activated,这个稍后再说
- idle 在 activated 以后就能够开始对 client 的请求进行拦截处理,sw 发起请求用的是 fetch api
- fetch 激活之后开始对网页中发起的请求进行拦截处理
- terminate 这一步是浏览器自身的判断处理,当 sw 长时间不用以后,处于闲置状态,浏览器会把该 sw 暂停,直到再次使
- update 浏览器会自动检测 sw 文件的更新,当有更新时会下载并 install,但页面中仍是老的 sw 在控制,只有当用户新开窗口后新的 sw 才能激活控制页面
fetch api
- 发送请求时,默认不会带上cookie,发送请求时若想带上cookie,得显示设定 { credential: 'include' }
- 对于跨域的资源,把模式设置为跨域 { mode: 'cors' },不然 response 中拿不到对应的数据
caches跨域
- 只能缓存 GET & HEAD 的请求,固然安全起见
- 以上,对于 POST 等类型请求,返回数据能够保存在 indexDB 中
serviceWorkerpromise
- 注册的 sw 资源文件,只能监听该 sw 的路径 & 以后子路径的请求,这个怎么理解呢:也就是若资源是 /app/sw.js ,打印出来 registration.scope === /app/ 则只能监听 /app/ 下的资源,不能监听其余 path,就连 /app 的也不行 !!!这意味着什么,意味着你在 /app 目录下注册的 /app/sw.js,访问 /app 时不会生效 !
![]()
- sw 提供了参数能够设定 scope 去设定监听的某一路径,那么咱们想让 /app 生效,得怎么作呢,其实就是得把 sw.js 放在根目录 / ,而后设置 { scope: '/app' } 就行了
- 在 sw 中 js 报错,不会被 client 的监控捕获到,所以,必需要专门对 sw 的错误进行处理
- 基于 a 可知:sw 注册文件,不能放在 CDN 上,必须在当前意图监听的 client 的 domain 下
- Request & Response 中的 body 只能被读取一次,究其缘由,是其中包含 bodyUsed 属性,当使用事后,这个属性值就会变为 true, 不能再次读取,解决方法是,把 Request & Response clone 下来: request.clone() || response.clone()
!(function (win) { const sw = win.navigator.serviceWorker const killSW = win.killSW || false if (!sw) { return } if (!!killSW) { sw.getRegistration('/serviceWorker').then(registration => { // 手动注销 registration.unregister() }) } else { // 表示该 sw 监听的是根域名下的请求 sw.register('/serviceWorker.js').then(registration => { // 注册成功后会进入回调 console.log(registration.scope) }).catch(err => { console.error(err) }) } })(window)
第一步:监听 install 事件,sw 基于事件驱动!浏览器
self.addEventListener('install', event => { console.log('installed') ... })
第二步:监听 activate 事件,sw install 以后不会当即生效,除非新打开页面,不然当前页面会一直是旧的 sw 掌控,所以有必要在 activate 后再对当前页面的缓存等进行必定的处理缓存
// 定义不一样 path 下的 cahche name const CACHE_NAME = 'TEST1' self.addEventListener('activate', event => { console.log('activated') event.waitUntil( // 删除旧文件 caches.keys().then(cacheNames => { return Promise.all( cacheNames.map((cacheName) => { return caches.delete(cacheName); }) ); }) ); })
浏览器缓存 caches 会一直保存存存存到存不动了,再去删除某些资源,这个是浏览器的行为,所以仍是建议在每次更改后去删除一些旧的浏览器资源,能够本身设定
第三步:开始监听页面发起的请求
sw 中用的是 fetch api 去请求相应的资源,但不表明 client 中得用 fetch ,全部页面的请求都会转变为 fetch 事件被 sw 捕获
event.respondWith 接收的是一个 promise 参数,把其结果返回到 client 中
fetch 分为三大模块 Header、Request、Response ,这里并不打算详说,能够自行去了解
self.addEventListener('fetch', event => { let { request } = event event.respondWith( // 先从 caches 中寻找是否有匹配 caches.match(request).then(res => { if (res) { return res } // 对于 CDN 资源要更改 request 的 mode if (request.mode !== 'navigate' && request.url.indexOf(request.referrer) === -1) { request = new Request(request, { mode: 'cors' }) } // 对于不在 caches 中的资源进行请求 return fetch(request).then(fetchRes => { // 这里只缓存成功 && 请求是 GET 方式的结果,对于 POST 等请求,可把 indexDB 给用上 if(!fetchRes || fetchRes.status !== 200 || request.method !== 'GET') { return fetchRes } let resClone = fetchRes.clone() caches.open(CACHE_NAME).then(cache => { cache.put(request, fetchRes) }) return resClone }) }) ) })
调试有几种方法:
未完待续 ...其实尚未真正把这个用到项目中去,sw 文件的放置路径就是个大问题,如今全部静态文件都在 CDN 上,得单独为它开个 VIP,能经过 client 的 host 直接访问到的;另外 饿了么 以前还很开心的宣布用上了 PWA ,可是最近不知道为啥给下线了,惧怕!