美文网首页
serviceWorker 入门

serviceWorker 入门

作者: ChanMQ | 来源:发表于2017-08-30 16:29 被阅读0次

    前提

    本文涉及几个知识点:fetch、caches、indexDB 等都不会详细介绍,仅对于其中某些点带过

    一. 概念

    serviceWorker,服务工作线程,顾名思义,只是作为工作线程存在,不掺和到JS主线程中来,介于 浏览器 & 服务器中间层,可拦截指定 client 所发起的所有请求

    二. 用途

    目前 PWA(Progress Web App) 的概念很火,大致就是让 web 也跟 app 一样,可以实现添加到桌面、消息推送、离线使用等功能,如 饿了么 在三月份左右就在H5上整了个 PWA 的页面。而其中的关键点,其实就是离线使用的功能,也就是 sw 在其中的作用。由于 sw 可以拦截 client 的请求,也就是能够根据请求,把请求后的 response 用浏览器缓存 caches 缓存下来,以实现离线的使用

    三. 生命周期

    说到 sw 的生命周期,就得祭奠出这张图了


    serviceWorker.png

    步骤分为以下部分:

    • register 这个是由 client 端发起,注册一个 serviceWorker,这需要一个专门的 sw 处理文件
    • install 注册成功后,此时 sw 中会触发 install 事件, 需知 sw 中都是事件触发的方式进行的逻辑调用
    • activate 安装后要等待激活,也就是 activated 事件,只要 register 成功后就会触发 install ,但不会立即触发 activated,这个稍后再说
    • idle 在 activated 之后就可以开始对 client 的请求进行拦截处理,sw 发起请求用的是 fetch api
    • terminate 这一步是浏览器自身的判断处理,当 sw 长时间不用之后,处于闲置状态,浏览器会把该 sw 暂停,直到再次使
    四. 目前发现存在的一些坑

    fetch

    • 发送请求时,默认不会带上cookie,发送请求时若想带上cookie,得显示设定 { credential: 'include' }
    • 对于跨域的资源,把模式设置为跨域 { mode: 'cors' },否则 response 中拿不到对应的数据

    caches

    • 只能缓存 GET & HEAD 的请求,当然安全起见
    • 以上,对于 POST 等类型请求,返回数据可以保存在 indexDB 中

    serviceWorker

    • 注册的 sw 资源文件,只能监听该 sw 的路径 & 之后子路径的请求,这个怎么理解呢:也就是若资源是 /js/sw.js ,则只能监听 /js 、/js/view 、/js/view/home 等,不能监听 / 下的资源
    • 可以设定 scope 去设定监听的某一路径,当然是建立在 a 基础之上
      在 sw 中 js 报错,不会被 client 的监控捕获到,因此,必须要专门对 sw 的错误进行处理
    • 基于 a 可知:sw 注册文件,不能放在 CDN 上,必须在当前意图监听的 client 的 domain 下
    • Request & Response 中的 body 只能被读取一次,究其原因,是其中包含 bodyUsed 属性,当使用过后,这个属性值就会变为 true, 不能再次读取,解决方法是,把 Request & Response clone 下来: request.clone() || response.clone()
    五. 动手实践篇
    1. client 端新建页面文件 index.js & sw 注册文件 serviceWorker.js
      把几个点都考虑好:渐进增强、出错降级
    !(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)
    
    1. 编写 serviceWorker.js 文件,注意 sw 的所有接口都是 promise 形式回调的

    第一步:监听 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
                })
            })
        )
    })
    
    1. 文件到这里就基本准备好了 ~ 写个 html 文件去调用 index.js 看看效果吧
    六. 如何调试

    调试有几种方法:

    1. 控制台 Application 中查看 sw 的生命


      image.png
    2. chrome://inspect/#service-workers 可查看当前打开的所有网站的 sw 资源,可以进行调试(但是其实直接在 source 中就可以进行调试的我发现,不需要这么麻烦 ==)

      image.png

    未完待续 ...
    其实还没有真正把这个用到项目中去,sw 文件的放置路径就是个大问题,现在所有静态文件都在 CDN 上,得单独为它开个 VIP,能通过 client 的 host 直接访问到的;
    另外 饿了么 之前还很开心的宣布用上了 PWA ,但是最近不知道为啥给下线了,害怕!

    相关文章

      网友评论

          本文标题:serviceWorker 入门

          本文链接:https://www.haomeiwen.com/subject/tlfxjxtx.html