资讯专栏INFORMATION COLUMN

serviceWorker 入门

lemanli / 1033人阅读

前提

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

一. 概念

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

二. 用途

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

三. 生命周期

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

步骤分为以下部分:

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 

发送请求时,默认不会带上cookie,发送请求时若想带上cookie,得显示设定 { credential: "include" }

对于跨域的资源,把模式设置为跨域 { mode: "cors" },否则 response 中拿不到对应的数据

caches

只能缓存 GET & HEAD 的请求,当然安全起见

以上,对于 POST 等类型请求,返回数据可以保存在 indexDB 中

serviceWorker

注册的 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()

五. 动手实践篇

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)

编写 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
            })
        })
    )
})

文件到这里就基本准备好了 ~ 写个 html 文件去调用 index.js 看看效果吧

六. 如何调试

调试有几种方法:

控制台 Application 中查看 sw 的生命

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

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

文章版权归作者所有,未经允许请勿转载,若此文章存在违规行为,您可以联系管理员删除。

转载请注明本文地址:https://www.ucloud.cn/yun/88356.html

相关文章

  • MobX入门TodoList

    摘要:用于简单可扩展的状态管理,相比有更高的灵活性,文档参考中文文档,本文作为入门,介绍一个简单的项目。任务已完成下一个任务修复谷歌浏览器页面显示问题提交意见反馈代码创建在中引入主入口文件设置参考入门学习总结 MobX用于简单、可扩展的React状态管理,相比Redux有更高的灵活性,文档参考:MobX中文文档,本文作为入门,介绍一个简单的TodoList项目。 1. 预期效果 showIm...

    csRyan 评论0 收藏0
  • 渐进式Web应用(PWA)入门教程(下)

    摘要:渐进式应用入门教程上在这一节中,我们将介绍的原理是什么,它是如何开始工作的。第一步使用渐进式应用程序需要使用连接。优先旋转方向,可选的值有显示方式无,和原生应用一样,最小的一套控件集或者最古老的使用浏览器标签显示一个包含所有图片的数组。 上篇文章我们对渐进式Web应用(PWA)做了一些基本的介绍。 渐进式Web应用(PWA)入门教程(上) 在这一节中,我们将介绍PWA的原理是什么,它是...

    NotFound 评论0 收藏0
  • pwa 实战总结

    摘要:现在表示公开支持。一旦安装完成,如果注册的没有变化,则显示为已激活的生命周期结束。一旦安装这步完成,便会激活,并控制在其范围内的一切。目前还在草案状态,仅火狐和谷歌浏览器支持此特性。 PWA初探 什么是PWA PWA(Progressive Web Apps):渐进式 Web app PWA 旨在增强 Web 体验,能让用户在访问一个web的时候感觉在使用app一样。 PWA可以看作是...

    xioqua 评论0 收藏0
  • Service Worker 浅析

    摘要:可以发送通知消息以再次吸引用户并留住他们。在即时通讯等使用情形中,一条消息可将最多的有效负载传送至客户端应用。浏览器的的消息推送主要依赖,服务端消息推送传递到,然后再由推送到客户端。 引言 Progressive Web App, 简称 PWA,是提升 Web App 的体验的一种新方法,能给用户原生应用的体验。Service Worker 是 PWA 中的重要一部分。Service ...

    The question 评论0 收藏0
  • ServiceWorker系列——ServiceWorker生命周期

    摘要:再次修改控制台输出也就是说激活过程中的任何错误不影响被激活脑洞下新激活过程中说明页面已经没有被其他控制了,所以事件回调函数的执行失败并不会影响被激活。 ServiceWorker生命周期 ServiceWorker本身是有状态的(installing,installed,activating,activated,redundant),这些状态构成了ServiceWorker生命周期:s...

    raledong 评论0 收藏0

发表评论

0条评论

最新活动
阅读需要支付1元查看
<