serviceWorker 入门

前提

本文涉及几个知识点: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 ,但是最近不知道为啥给下线了,害怕!

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 194,390评论 5 459
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 81,821评论 2 371
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 141,632评论 0 319
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 52,170评论 1 263
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 61,033评论 4 355
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 46,098评论 1 272
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 36,511评论 3 381
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 35,204评论 0 253
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 39,479评论 1 290
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 34,572评论 2 309
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 36,341评论 1 326
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 32,213评论 3 312
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 37,576评论 3 298
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 28,893评论 0 17
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 30,171评论 1 250
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 41,486评论 2 341
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 40,676评论 2 335

推荐阅读更多精彩内容