一文了解Service Worker

简介

首先了解一下PWA(Progressive web apps,渐进式 Web 应用)运用现代的 Web API 以及传统的渐进式加强策略来建立跨平台 Web 应用程序。 PWA的优势 PWA可被发现易安装可连接独立于网络渐进式可重用响应性安全的。 PWA中能够经过Service Worker来实现离线的应用,这个也是PWA中一个比较重要的环节,它们主要应用到Web App中,已得到更好的体验,而且在如今也在大规模的应用。 Service Worker是一个事件驱动worker,运行在一个单独的后台进程,是PWA(ProgressiveWeb App)运行的基础。主要用于代理网页请求,可缓存请求结果;可实现离线缓存功能,也拥有单独的做用域范围和运行环境。咱们之后把Service Worker简称为SWjavascript

本文大体分为:css

  • SW特性
  • SW生命周期和使用
  • SW中的缓存策略
  • SW一些注意事项
  • SW几种缓存策略
  • SW中的消息推送
  • Workerbox使用

实例代码实例代码地址html

SW的特性

它们的运行在一个与咱们页面的 JavaScript 主线程独立的线程上,而且没有对 DOM 结构的任何访问权限。 这引入了与传统 Web 编程不一样的方法 - API 是非阻塞的,而且能够在不一样的上下文之间发送和接收信息前端

SW使用限制

SW除了work线程的限制外,因为可拦截页面请求,为了保证页面安全,浏览器端对sw的使用限制也很多。java

  • 没法直接操做DOM对象,也没法访问windowdocumentparent对象。能够访问navigatorlocationSW 经过响应 postMessage 接口发送的消息来与其控制的页面通讯,页面可在必要时对 DOM 执行操做。
  • 可代理的页面做用域限制。默认是sw.js所在文件目录及子目录的请求可代理,可在注册时手动设置做用域范围;
  • 必须https 中使用,容许在开发调试的localhost使用。

SW主要做用

  • 能够用来作缓存,以达到提高体验、节省浏览等等
  • SW 是一种可编程网络代理,让您可以控制页面所发送网络请求的处理方式。
  • 离线缓存接口请求及文件,更新、清除缓存内容;
  • 可分配给 Service Worker 一些任务,并在使用基于 Promise 的方法当任务完成时收到结果。
  • Service Worker处于空闲状态会被终止,在下一次须要时重启。

SW兼容性

能够经过查询service worker能够看到他在不一样平台或不一样浏览器中的兼容性。git

SW生命周期和使用

SW 的生命周期彻底独立于网页。 SW 为网页添加一个相似于 App 的生命周期,它只会响应系统事件,就算浏览器关闭时操做系统也能够唤醒 SW,这点很是重要,让Web AppNative App 的能力变得相似了。因为是离线缓存,因此在初始安装时更新它们的所走的生命周期是不相同。下面咱们就根据这两种场景结合代码来分析它的执行步骤。 SW的生命周期大体分为:注册更新安装成功安装失败激活销毁github

使用SW前提条件web

  • 必须https 中使用,容许在开发调试的localhost使用。
  • 浏览器必须支持SW

初始安装时

初始安装时大体流程大体以下图: 正则表达式

http-cache-serviceworker

大体能够分为注册SW => 安装SW => 激活 => 空闲 => (缓存和返回请求/终止),在初始安装时会大体分为这几个步骤,下面就按照这几个步结合代码实现。chrome

注册 Service Worker

用户首次访问SW控制的网站或页面时,sw.js会马上被下载和解析。咱们要在页面中写入JavaScript来注册SW

// 判断浏览器是否支持serviceWorker
    if ('serviceWorker' in navigator) {
        // 在页面加载后
        window.addEventListener('load', function () {
            // 经过navigator.serviceWorker.register 注册'./sw.js
            navigator.serviceWorker.register('./sw.js')
            .then(reg => { //注册成功
                console.log('注册成功', reg)
            }).catch(err => { //注册成功
                console.log('注册失败', err)
            })
        });
    } else {
        console.log('当前浏览器不支持SW')
    }
复制代码

首先检浏览器是否支持SW,若是支持就在浏览器加载后经过register().then注册sw.js,而且设置注册成功或者失败的回调函数。

注意:register() 方法的精妙之处在于服务工做线程文件的位置。SW降接收此网域上全部的事项的featch事件。 若是是Chrome浏览器能够经过chrome://inspect/#service-workers或者console => application => Service Worker查看是否注册成功

由于如今sw.js中咱们的代码是空的,因此在浏览器中的cache stoage是空的,运行效果以下:

安装 Service Worker

在受控页面启动注册流程后,下面就是SW获取的第一个事件install,而且只发生一次。传递到 installEvent.waitUntil() 的一个 promise 可代表安装的持续时间以及安装是否成功。

install中要作三件事打开缓存缓存文件确认全部须要的资产是否已缓存

// 在sw.js中监听对应的安装事件,并预处理须要缓存的文件
    // 该部份内容涉及到cacheStorage API

    // 定义缓存空间名称
    const CACHE_NAME = 'sw_cache_v1';
    // 定义须要缓存的文件目录
    let cachelist = ['./app.js', './index.css'];
    // 监听安装事件,返回installEvent对象
    self.addEventListener('install', function (installEvent) {
        // waitUntil方法执行缓存方法
        installEvent.waitUntil(
            // cacheStorage API 可直接用caches来替代
            // open方法建立/打开缓存空间,并会返回promise实例
            // then来接收返回的cache对象索引
            caches.open(CACHE_NAME)
             // cache对象addAll方法解析(同fetch)并缓存全部的文件
            .then(function(cache) {
                console.log('Opened cache');
                return cache.addAll(cachelist);
            })
        );
    });

复制代码

第一个事件为install,该事件在Worker执行时当即触发。在install的回调函数中,咱们经过caches.open(CACHE_NAME)打开缓存,以后调用cache.addAll()并传入路径数组。这是一个promise链(caches.open()chaches.addAll()installEvent.waitUntill()放大带有promise并使用它来判断安装所花时间,以及是否安装成功。

注意: 第一个事件install,它只能被每一个 SW 调用一次。若是您更改您的 SW 脚本,则浏览器将其视为一个不一样SW,而且它将得到本身的 install 事件。 若有任何文件没法下载,则安装步骤将失败。 当前的状态是在等待状态。 咱们能够直接经过self.skipwaiting()让当前sw当即将状态提高到active

当咱们安装成功时,效果以下图所示:

http-cache-serviceworker

会多了一个skipWaiting,还有在cache stroage中的当前域名下的service worker对应的缓存文件列表。这个时候咱们即便刷新也不会走service worker的缓存的。

激活

SW 准备控制客户端并处理 pushsync 等功能事件时,您将得到一个 activate 事件。但这不意味着调用 .register() 的页面将受控制。若是第二次加载此演示(换言之,刷新页面),该页面将受控制。改写代码sw.js以下:

self.addEventListener('install', () => {
        // 通常注册之后,激活须要等到再次刷新页面后再激活
        // 可防止出现等待的状况,这意味着服务工做线程在安装完后当即激活
        self.skipWaiting();
    })
    self.addEventListener('activate', function (event) {
        event.waitUntil(
            // cacheStorage API 可直接用caches来替代
            // open方法建立/打开缓存空间,并会返回promise实例
            // then来接收返回的cache对象索引
            caches.open(CACHE_NAME)
            // cache对象addAll方法解析(同fetch)并缓存全部的文件
            .then(function(cache) {
                console.log('Opened cache');
                return cache.addAll(cachelist);
            })
        );
    })
复制代码

通常在书写的时候,会在install()注册以后直接经过self.skipWaiting();激活当前的SW,在activate中书写打开缓存等等的逻辑,就不会出现上面还要刷新或者手动激活的问题。效果图以下:

http-cache-serviceworker

可是若是出现更新SW,而且更新了缓存列表或者出现异步资源时,咱们能够经过clients.claim()更新缓存列表。

clients.claim

激活 SW 后,您能够经过在其中调用 clients.claim() 控制未受控制的客户端。google developer中的一个异步加载图片的实例。下面修改代码以下:

self.addEventListener('install', (event) => {
        event.waitUntil(
            // cacheStorage API 可直接用caches来替代
            // open方法建立/打开缓存空间,并会返回promise实例
            // then来接收返回的cache对象索引
            caches.open(CACHE_NAME)
            // cache对象addAll方法解析(同fetch)并缓存全部的文件
            .then(function(cache) {
                console.log('Opened cache');
                return cache.addAll(cachelist);
            })
        );
        // 通常注册之后,激活须要等到再次刷新页面后再激活
        // 可防止出现等待的状况,这意味着服务工做线程在安装完后当即激活
        self.skipWaiting();
    })
    self.addEventListener('activate', function (event) {
        // 若缓存数据更改,则在这里更新缓存
        var cacheDeletePromise = caches.keys()
        .then(keyList => {
            Promise.all(keyList.map(key => {
                if (key !== CACHE_NAME) {
                    var deletePromise = caches.delete(key)
                    return deletePromise
                } else {
                    Promise.resolve()
                }
            }));
        });
        event.waitUntil(
            Promise.all([cacheDeletePromise]).then(res => {
                this.clients.claim()
            })
        );
    })
复制代码

用于处理更新缓存,新的文件。到如今咱们仍是没有用到serviceWorker的缓存,下面重头戏来了缓存和返回请求

缓存和返回请求

上咱们已经安装而且激活了SW,如今咱们要返回一个缓存的响应。SW用户转至其余页面或刷新当前页面后,将开始接受fetch事件。

self.addEventListener('fetch', function(event) {
        event.respondWith(
            caches.match(event.request)
            .then(function(response) {
                // Cache hit - return response
                if (response) {
                    return response;
                }
                return fetch(event.request);
            })
        );
    });
复制代码

在定义的fetch事件中,咱们在event.respondWith()中传入来自caches.match()的一个promisecaches.match()这个方法检视该请求,并从服务工做线程所建立的任何缓存中查找缓存的结果。若是命中返回缓存值,不然,将调用fetch以发出网络请求。运行效果以下:

http-cache-serviceworker

若是咱们想把新的请求也缓存掉,修改代码以下:

self.addEventListener('fetch', function(event) {
        event.respondWith(
            caches.match(event.request)
            .then(function(response) {
                // Cache hit - return response
                if (response) {
                    return response;
                }
                // return fetch(event.request);
                var requestClone = event.request.clone();
                return fetch(requestClone).then(response => {
                    if(!response || response.status !== 200 || response.type !== 'basic') {
                        return response;
                    }
                    var responseToCache = response.clone();
                    caches.open(CACHE_NAME)
                    .then(function(cache) {
                        cache.put(event.request, responseToCache);
                    });
                    return response;
                });
            })
        );
    });
复制代码

执行操做以下:

  1. fetch 请求中添加对 .then() 的回调。
  2. 得到响应后,确保响应有效。检查并确保响应的状态为 200。确保响应类型为 basic,亦即由自身发起的请求。 这意味着,对第三方资产的请求也不会添加到缓存。
  3. 若是经过检查,则克隆响应。

没有缓存新请求时效果以下:

http-cache-serviceworker

当使用咱们下面的代码时,效果图以下:

http-cache-serviceworker

即便异步请求的png图片也被加入了缓存中。

更新SW

在如下状况下会触发更新:

  • 导航到一个做用域内的页面。
  • 更新 pushsync 等功能事件,除非在前 24 小时内已进行更新检查。
  • 调用 .register(),仅在 SW 网址已发生变化时。

当触发更新时,会通过大体以下步骤:

  1. 更新您的服务工做线程 JavaScript 文件。 用户导航至您的站点时,浏览器会尝试在后台从新下载定义 SW 的脚本文件。 若是 SW 文件与其当前所用文件存在字节差别,则将其视为新 SW
  2. 更新SW现有 SW 一块儿启动,并获取本身的 install 事件。
  3. 此时, SW控制着当前页面,所以 SW 将进入 waiting 状态。
  4. 若是新 Worker 出现不正常状态代码(例如,404)、解析失败,在执行中引起错误在安装期间被拒,则系统将舍弃新 Worker,但当前 Worker 仍处于活动状态
  5. 安装成功后,更新的 Worker 将 wait,直到现有 Worker 控制零个客户端。(注意,在刷新期间客户端会重叠。)
  6. self.skipWaiting() 可防止出现等待状况,这意味着 Service Worker 在安装完后当即激活。

更新SW代码

更新一个叫作sw_cache_v2的新的SW缓存,代码以下:

const CACHE_NAME = 'sw_cache_v2';

    self.addEventListener('install', (event) => {
        event.waitUntil(
            // cacheStorage API 可直接用caches来替代
            // open方法建立/打开缓存空间,并会返回promise实例
            // then来接收返回的cache对象索引
            caches.open(CACHE_NAME)
            // cache对象addAll方法解析(同fetch)并缓存全部的文件
            .then(function(cache) {
                return cache.add('index_copy.png')
            })
        );
        // 通常注册之后,激活须要等到再次刷新页面后再激活
        // 可防止出现等待的状况,这意味着服务工做线程在安装完后当即激活
        self.skipWaiting();
    })
    self.addEventListener('activate', function (event) {
        // 若缓存数据更改,则在这里更新缓存
        var cacheDeletePromise = caches.keys()
        .then(keyList => {
            Promise.all(keyList.map(key => {
                if (key !== CACHE_NAME) {
                    var deletePromise = caches.delete(key)
                    return deletePromise
                } else {
                    Promise.resolve()
                }
            }));
        });
        event.waitUntil(
            Promise.all([cacheDeletePromise]).then(res => {
                this.clients.claim()
            })
        );
    });
    self.addEventListener('fetch', function(event) {
        event.respondWith(
            caches.match(event.request)
            .then(function(response) {
                // Cache hit - return response
                if (response) {
                return response;
                }
                return fetch(event.request);
            }
            )
        );
    });
复制代码

代码执行效果以下图所示:

http-cache-serviceworker

整个过程咱们大体通过了install => waiting => activate三个过程。

更新Install

咱们在代码中把sw_cache_v1更改成sw_cache_v2,咱们从新Install了一个新的缓存sw_cache_v2,而且经过添加了一个缓存进去cache.add('index_copy.png')

更新Waiting

若是新的缓存安装成功SW后,更新的SW将延迟激活,直到现有SW再也不控制任何客户端。此状态为waiting,这是浏览器确保每次只运行一个SW版本的样式。

激活Activate

SW 退出时将触发 Activate,新 SW 将可以控制客户端。此时,您能够执行在仍使用旧 Worker 时没法执行的操做,如迁移数据库和清除缓存。 在上面的演示中,我维护了一个指望保存的缓存列表,而且在 activate事件中,我删除了全部其余缓存,从而也移除了旧的 sw_cache_v1 缓存。

不要更新之前的版本。它多是许多旧版本的 SW

若是您将一个 promise 传递到 event.waitUntil(),它将缓冲功能事件(fetch、push、sync 等),直到 promise 进行解析。所以,当您的 fetch 事件触发时,激活已所有完成。

Cache storage API 属于“源存储”(如 localStorageIndexedDB)。若是您在同源上运行许多网站(例如,yourname.github.io/myapp),请注意,不要删除其余网站的缓存。为避免此问题,能够为您的缓存名称提供一个在当前网站上具备惟一性的前缀(例如,myapp-static-v1),而且不要删除缓存,除非它们以 myapp- 开头。

跳过等待阶段skipWaiting

等待阶段表示您每次只能运行一个网站版本,但若是您不须要该功能,您能够经过调用 self.skipWaiting() 尽快将新 SW 激活。 这会致使您的 SW 将当前活动的 Worker 逐出,并在进入等待阶段时尽快激活本身(或当即激活,前提是已经处于等待阶段)。这不能让您的 Worker 跳过安装,只是跳过等待阶段skipWaiting() 在等待期间调用仍是在以前调用并无什么不一样。通常状况下是在 install 事件中调用它:

self.addEventListener('install', event => {
        self.skipWaiting();
        event.waitUntil(
            // caching etc
        );
    });
复制代码

clients.claim() 同样,它是一个竞态。

skipWaiting() 意味着新 Service Worker 可能会控制使用较旧 Worker 加载的页面。这意味着页面提取的部分数据将由旧 Service Worker 处理,而新 Service Worker 处理后来提取的数据。若是这会致使问题,则不要使用 skipWaiting()

手动更新update

当页面刷新或者执行功能性事件时,浏览器会自动检查更新,其实咱们也能够手动的来触发更新:

navigator.serviceWorker.register("/sw.js").then(reg => {
        // sometime later…
        reg.update();
    });
复制代码

若是你但愿你的用户访问页面很长时间并且不用刷新,那么你能够每一个一段时间调用一次update()

避免改变 SW 的 URL

你可能会考虑给每一个 SW 不一样的 URL。**千万不要这么作!**在 SW 中这么作是“最差实践”,要在原地址上修改 SW

举个例子来讲明为何:

  1. index.html注册了sw-v1.js做为SW

  2. sw-v1.jsindex.html作了缓存,也就是缓存优先(offline-first)。

  3. 你更新了index.html从新注册了在新地址的 SW sw-v2.js.

若是你像上面那么作,用户永远也拿不到sw-v2.js,由于index.htmlsw-v1.js缓存中,这样的话,若是你想更新为sw-v2.js,还须要更改原来的sw-v1.js

SW一些注意事项

这里主要分为:

  • 更新的小技巧
  • sync事件

更新小技巧

SW 生命周期是专为用户构建的,这就给开发工做带来必定的困难。幸运的是,咱们可经过如下几个工具解决这个问题:

Update on reload

http-cache-serviceworker

这可以使生命周期变得对开发者友好。每次浏览时都将:

  1. 从新提取 SW
  2. 即便字节彻底相同,也将其做为新版本安装,这表示运行 install 事件并更新缓存
  3. 跳过等待阶段,以激活SW
  4. 浏览页面。这意味着每次浏览时(包括刷新)都将进行更新,无需从新加载两次或关闭标签。

Skip waiting

http-cache-serviceworker

若是您有一个 Worker 在等待,您能够按 DevTools 中的“skip waiting”以当即将其提高到“active”。同时也能够经过self.skipWaiting()来实现。

Shift-reload

若是您强制从新加载页面 (shift-reload),则将彻底绕过 SW。页面将变得不受控制。此功能已列入规范,所以,它在其余支持 SW 的浏览器中也适用。

处理更新周期

为支持尽量多的模式,整个更新周期都是可观察的:

navigator.serviceWorker.register('/sw.js').then(reg => {
        reg.installing; // the installing worker, or undefined
        reg.waiting; // the waiting worker, or undefined
        reg.active; // the active worker, or undefined

        reg.addEventListener('updatefound', () => {
            // A wild service worker has appeared in reg.installing!
            const newWorker = reg.installing;

            newWorker.state;
            // "installing" - the install event has fired, but not yet complete
            // "installed" - install complete
            // "activating" - the activate event has fired, but not yet complete
            // "activated" - fully active
            // "redundant" - discarded. Either failed install, or it's been
            // replaced by a newer version

            newWorker.addEventListener('statechange', () => {
            // newWorker.state has changed
            });
        });
    });

    navigator.serviceWorker.addEventListener('controllerchange', () => {
        // This fires when the service worker controlling this page
        // changes, eg a new worker has skipped waiting and become
        // the new active worker.
    });
复制代码

sync事件

Sync事件让你能够先将网络相关任务延迟到用户有网络的时候再执行。这个功能常被称做“背景同步”。这功能能够用于保证任何用户在离线的时候所产生对于网络有依赖的操做,最终能够在网络再次可用的时候抵达它们的目标。

代码示例以下

navigator.serviceWorker.ready.then(registration => {
        document.getElementById('submit').addEventListener('click', () => {
            registration.sync.register('submit').then(() => {
                console.log('sync registered!');
            });
        });
    });
复制代码

咱们指定在一个按钮的点击事件里,在一个全局的 ServiceWorkerRegistration 对象身上调用 sync.register

简单地讲,任何你须要确保在有网络时马上执行或者等到有网再执行的操做,都须要注册为一个sync事件

这操做能够是发送一个评论,或者获取用户信息,在SW的事件监听器里会以下定义:

// sw.js
    self.addEventListener('sync', event => {  
        if (event.tag === 'submit') {
            console.log('sync!');
        }
    });
复制代码

咱们监听一个 sync 事件,而后在 SyncEvent 对象上检查 tag 是否匹配咱们在点击事件里所设定的 'submit'

若是多个 tag 标记为 submitsync事件被注册了,sync 事件处理器只会运行一次。

因此在这个例子里,若是用户离线了,而后点击按钮7次,当网络再次连上,全部的sync注册都会合而为一,sync事件只会触发一次。

Sync事件是何时触发

若是用户的网络时联通的,那么sync事件马上触发而且马上执行你所定义的任务。

而若是用户离线了,sync 事件会在网络恢复后第一时间触发

SW几种缓存策略

  • 渐进式缓存
  • 仅使用缓存
  • 仅使用网络
  • 缓存优先
  • 网络优先
  • 速度优先

渐进式缓存

对于在install中发现没有缓存,页面又依赖但又不常常变化的资源,能够在页面打开或发生用户交互时触发fetch而后使用fetch api再去网络拉取,将返回正常的response缓存起来以便下次使用。

progressive-cache

self.addEventListener('fetch', function(event) {
        event.respondWith(
            caches.match(event.request)
            .then(function(response) {
                // Cache hit - return response
                if (response) {
                    return response;
                }
                // return fetch(event.request);
                var requestClone = event.request.clone();
                return fetch(requestClone).then(response => {
                    if(!response || response.status !== 200 || response.type !== 'basic') {
                        return response;
                    }
                    var responseToCache = response.clone();
                    caches.open(CACHE_NAME)
                    .then(function(cache) {
                        cache.put(event.request, responseToCache);
                    });
                    return response;
                });
            })
        );
    });
复制代码

仅使用缓存

fetch事件中,仅去匹配资源,若匹配失败,表现出来的就是前端页面对于该 资源加载失败。这里容错性比较差,适用于页面资源都是静态资源的,且不能使用不影响安装的资源预缓存

cache-only

// SW请求拦截事件
    self.addEventListener('fetch', (event) => {
        event.respondWith(
            caches.open(OFFLINE_CACHE_NAME).then((cache) => {
                // 匹配资源若是命中返回缓存
                return cache.macth(event.request.url);
            });
        );
    });
复制代码

仅使用网络

fetch事件中,仅将request从新抽出用fetch去网络加载并返回给前端页面。适用于资源大可能是动态资源实时性要求高的场景。

network-only

// SW请求拦截事件
    self.addEventListener('fetch', (event) => {
        // 仅使用网络加载
        event.respondWith(fetch(event.request));
    });
复制代码

缓存优先

简单的资源缓存中使用的就是缓存优先策略先去缓存匹配匹配失败折回网络,这算是最经常使用、容错性能好的一种策略。

firstCache

function firstCache (cacheName, request) {
        // 打开SW
        return caches.open(cacheName).then(cache => {
            // 匹配请求路径
            return cache.match(request).then(response => {
                // fetch请求
                var fetchServer = function() {
                    return fetch(request).then(newResponse => {
                        cache.put(request, newResponse.clone());
                        return newResponse;
                    });
                }
                // 若是缓存中有数据则返回,不然请求网络数据
                if (response) {
                    return response;
                } else {
                    return fetchServer();
                }
            });
        });
    }
复制代码

网络优先

fetch事件中先去网络fetch,当出现服务器故障或者网络不良时折回本地缓存,目的是为了展现最新的数据,对实时性要求比较高但又可以带来良好体验的应用,好比天气类型应用。

firstNet

function firstNet(cacheName, request) {
        // 请求网络数据并缓存
        return fetch(request).then(response => {
            // 响应clone
            var responseCopy = response.clone();
            caches.open(cacheName).then(cache => {
                // fetch请求
                cache.put(request, responseCopy);
            });
            return response;
        }).catch(() => {
            // fetch失败走本地缓存
            return caches.open(cacheName).then(cache => {
                return cache.match(request);
            });
        });
    }
复制代码

速度优先

fetch事件同时发起本地缓存匹配及网络请求谁先返回使用谁的,该方案适用于对性能要求比较高的站点,缩短了缓存优先策略中有可能缓存中没有资源再折回网络的时间消耗。

function networkCacheRace(cacheName, request) {
    var timer, TIMEOUT = 500;
    /** * 网络好的状况下给网络请求500ms, 若超时则从缓存中取数据 * 若网络较差且没有缓存, 因为第一个 Promise 会一直处于 pending, 故此时等待网络请求响应 */
    return Promise.race([new Promise((resolve, reject) => {
        // 缓存请求
        timer = setTimeout(() => {
            caches.open(cacheName).then( cache => {
                cache.match(request).then( response => {
                    if (response) {
                        resolve(response);
                    }
                });
            });
        }, TIMEOUT);
    }), fetch(request).then( response => {
        // 网络请求
        clearTimeout(timer);
        var responseCopy = response.clone();
        caches.open(cacheName).then( cache => {
            cache.put(request, responseCopy);
        });
        return response;
    }).catch(() => {
        clearTimeout(timer);
        return caches.open(cacheName).then( cache => {
            return cache.match(request);
        });
    })]);
}
复制代码

如今咱们能够在 sw.js 中更改一下缓存策略,从而达到最理想的效果。

// sw.js
    self.addEventListener('fetch', event => {
        // ...
        if ( /\.(js|css)$/.test(url) ) {
            (cacheName = cacheMaps.cache_file) 
            && e.respondWith(networkCacheRace(cacheName, request));
        }
        // ...
    })
复制代码

SW中的消息推送

Push消息

SW里,经过 push 事件以及浏览器的 Push API,能够实现push消息的功能。 在说道web push消息的时候,其实涉及到两个正在完善中的技术:消息提醒信息推送

消息提醒

SW实现消息提醒挺简单直接:

// app.js
    // ask for permission
    Notification.requestPermission(permission => {  
        console.log('permission:', permission);
    });

    // display notification
    function displayNotification() {  
        if (Notification.permission == 'granted') {
            navigator.serviceWorker.getRegistration()
            .then(registration => {
                registration.showNotification('this is a notification!');
            });
        }
    }
复制代码
// sw.js
    self.addEventListener('notificationclick', event => {  
        // 消息提醒被点击的事件
    });

    self.addEventListener('notificationclose', event => {  
        // 消息提醒被关闭的事件
    });
复制代码

你须要先向用户寻求让你的网页产生消息提醒的权限。以后,你就能够弹出提示信息,而后处理某些事件,好比用户把消息关掉的事件。

信息推送

信息推送涉及到利用浏览器提供的Push API以及后端的配合实现。要讲解如何使用Push API彻底能够再写一篇文章,不过基本的套路以下:

http-cache-serviceworker

这是个略微复杂难懂的过程,已经超出这篇文章的讨论范围。

更好的方案 - Workbox

什么是 Workbox ?

Workbox is a library that bakes in a set of best practices and removes the boilerplate every developer writes when working with service workers.

其大概意思是它对常见的 SW 操做进行了一层封装, 根据最佳实践方便了开发者的使用。所以在咱们快速开发本身的 PWA 应用时使用 Workbox 是最合适不过的了。

它主要有如下几大功能 :

  • Precaching ~ 预缓存
  • Runtime caching ~ 运行时缓存
  • Strategies ~ 缓存策略
  • Request routing ~ 请求路由控制
  • Background sync ~ 后台同步
  • Helpful debugge

简单应用

直接修改sw.js的代码,以下:

// sw.js
    // 导入谷歌提供的 Workbox 库
    importScripts('https://storage.googleapis.com/workbox-cdn/releases/3.2.0/workbox-sw.js');

    if ( !workbox ) { 
        console.log(`Workbox didn't load.`);
        return;
    }

    // Workbox 注册成功, 能够进行下一步的操做

    // 当即激活, 跳过等待
    workbox.skipWaiting();
    workbox.clientsClaim();

    // workbox.routing.registerRoute()...
复制代码

若是浏览器支持,能够直接引用API接口:

  1. precaching能够在注册成功后直接缓存的文件;
  2. routing匹配符合规则的urlstrategies合做来完成文件的缓存。

代码以下:

// 注册完成后,即缓存对应的文件列表
workbox.precaching.precacheAndRoute([
    '/src/static/js/index.js',
    '/src/static/css/index/css'
])

// routing方法匹配请求文件路径,strategies用来存储对应文件
workbox.routing.registerRoute(
    matchFunction,  // 字符串或者是正则表达式
    handler // 可使用workbox.strategies缓存策略来缓存
)
复制代码

workbox缓存策略

workbox.strategies缓存策略有:

  1. staleWhileRevalidate 使用已有的缓存,而后发起请求,用请求结果来更新缓存;
  2. networkFirst 先发起请求,请求成功后会缓存结果。若是失败,则使用最新的缓存;
  3. cacheFirst 老是先使用缓存,若是无匹配的缓存,则发起网络请求并缓存结果;
  4. networkOnly 强制发起请求;
  5. cacheOnly 强制使用缓存。

官方也有给了实现逻辑以下。

Cache Only

只从缓存中读取,当缓存中没有数据时,读取失败。

http-cache-serviceworker

NetWork Only

只经过网络请求进行资源请求,若请求失败,则返回失败响应。

http-cache-serviceworker

NetWork First

  • 优先网络请求
  • 网络请求成功时,将结果写入缓存,并将结果直接返回。
  • 网络请求失败时,从缓存中读取结果,若读取结果,则返回,若未读取到,则请求失败。

不难看出,这种策略是为了保证在第一次请求成功以后,后面屡次的请求始终都能返回结果。

http-cache-serviceworker

workbox.routing.registerRoute(/\.(js|css)$/,
        workbox.strategies.networkFirst({
            // 给网络请求0.5秒,若仍未返回则从缓存中取数据
            networkTimetoutSeconds: 0.5,
            cacheName: 'css.js',
        }),
    );
复制代码

Cache First

  • 优先从缓存中读取结果
  • 若缓存中不存在结果,则进行网络请求
  • 网络请求成功时,将结果写入缓存并返回
  • 网络请求失败时,返回失败响应

http-cache-serviceworker

workbox.routing.registerRoute(/\.(png|jpg|jpeg|gif|webp)$/,
        // 对于图片资源使用缓存优先
        workbox.strategies.cacheFirst({
            cacheName: 'images',
            // 设置最大缓存数量以及过时时间
            plugins: [
            new workbox.expiration.Plugin({
                maxEntries: 60,
                maxAgeSeconds: 7 * 24 * 60 * 60,
            }),
            ],
        }),
    );
复制代码

Stale-While-Revalidate

  • 优先查缓存,并同时发起网络请求
  • 若缓存命中且网络请求成功,返回缓存结果,并更新缓存(下次从缓存中读取的数据就是最新的了)
  • 若缓存未命中,则看网络请求是否成功,成功则更新缓存并返回结果,失败则返回失败响应。

http-cache-serviceworker

workbox.routing.registerRoute(/\.(js|css)$/,
        workbox.strategies.staleWhileRevalidate({
            cacheName: 'css.js',
        }),
    );
复制代码

总结

在本篇文章中详细的记录了有关SW的主要功能和能给咱们带来的好处。通常在开发中仍是推荐使用workbox,最后若是我的有兴趣的能够本身编写示例来验证文章中的代码示例。

SW主要做用

  • 能够用来作缓存,以达到提高体验、节省浏览等等
  • SW 是一种可编程网络代理,让您可以控制页面所发送网络请求的处理方式。
  • 离线缓存接口请求及文件,更新、清除缓存内容;
  • 可分配给 Service Worker 一些任务,并在使用基于 Promise 的方法当任务完成时收到结果。
  • Service Worker处于空闲状态会被终止,在下一次须要时重启。

SW几种策略

  • 渐进式缓存
  • 仅使用缓存
  • 仅使用网络
  • 缓存优先
  • 网络优先
  • 速度优先

感受写的不错请点一下赞,据说长的帅的人都会点赞,而且点赞后走上人生巅峰

参考

Service Worker:简介  |  Web Fundamentals  |  Google Developers

Service Worker 生命周期  |  Web Fundamentals  |  Google Developers

用 Service Worker 实现前端性能优化

service worker 实现离线缓存

【前-workbox-网络摘要】WorkBox缓存策略

相关文章
相关标签/搜索