Service Worker

Service Worker初体验

2016/01/06 · JavaScript
· Service Worker

原文出处: AlloyTeam   

在2014年,W3C公布了service worker的草案,service
worker提供了很多新的能力,使得web app拥有与native
app相同的离线体验、消息推送体验。
service worker是一段脚本,与web
worker一样,也是在后台运行。作为一个独立的线程,运行环境与普通脚本不同,所以不能直接参与web交互行为。native
app可以做到离线使用、消息推送、后台自动更新,service
worker的出现是正是为了使得web app也可以具有类似的能力。

 

service worker可以:

  1. 后台消息传递
  2. 网络代理,转发请求,伪造响应
  3. 离线缓存
  4. 消息推送
  5.  … …

本文以资源缓存为例,说明一下service worker是如何工作的。

简书放不了demo,demo可以看原文

作用:

可以使你的应用先访问本地缓存资源,所以在离线状态时,在没有通过网络接收到更多的数据前,仍可以提供基本的功能。

Service Worker是什么


从功能上来说,Service Worker是一种提供离线缓存控制功能的一种Worker,同时,也具有消息推送和后台同步的功能,可以通过Service
Worker来缓存网页的资源,然后拦截Fetch请求来执行相应的缓存处理操作。因为是一种Worker,所以Service
Worker也具有Worker的一些基本特性,例如:

  • 独立于主线程运行
  • 不能访问window对象,但是拥有自身的一个执行上下文,例如Service WorkerServiceWorkerGlobalScope
  • 具有消息api来和页面进行消息交互

Service
Worker是一种共享型Worker,它不同于专用型Worker只能在创建它的页面中使用,默认配置下,service
Worker可以被当前注册脚本的域名下所有页面公用。
也就是说,只要在一个根域名注册一个ServiceWorker,那么所有这个域名下的页面都会收到影响。

原文

生命周期

先来看一下一个service worker的运行周期

图片 1
上图是service
worker生命周期,出处

图中可以看到,一个service worker要经历以下过程:

  1.  安装

2.
 激活,激活成功之后,打开chrome://inspect/#service-workers可以查看到当前运行的service
worker

图片 2

  1. 监听fetch和message事件,下面两种事件会进行简要描述

  2. 销毁,是否销毁由浏览器决定,如果一个service
    worker长期不使用或者机器内存有限,则可能会销毁这个worker

Service Worker 是什么?

service worker 是独立于当前页面的一段运行在浏览器后台进程里的脚本。
service worker不需要用户打开 web
页面,也不需要其他交互,异步地运行在一个完全独立的上下文环境,不会对主线程造成阻塞。基于service
worker可以实现消息推送,静默更新以及地理围栏等服务。
service
worker提供一种渐进增强的特性,使用特性检测来渐渐增强,不会在老旧的不支持
service workers 的浏览器中产生影响。可以通过service
workers解决让应用程序能够离线工作,让存储数据在离线时使用的问题。

注意事项:
1.service
worker运行在它们自己的完全独立异步的全局上下文中,也就是说它们有自己的容器。
2.service
worker没有直接操作DOM的权限,但是可以通过postMessage方法来与Web页面通信,让页面操作DOM。
3.service
worker是一个可编程的网络代理,允许开发者控制页面上处理的网络请求。
4.浏览器可能随时回收service
worker,在不被使用的时候,它会自己终止,而当它再次被用到的时候,会被重新激活。
5.service worker的生命周期是由事件驱动的而不是通过Client。

使用前的设置:

Chrome中需要开启相关配置: 访问
chrome://flags 并开启
experimental-web-platform-features; 重启浏览器
(注意:有些特性在Chrome中没有默认开放支持);另外,你需要通过 HTTPS
来访问你的页面 — 出于安全原因,Service Workers 要求要在必须在 HTTPS
下才能运行,localhost 也被浏览器认为是安全源。

Service Worker使用


要使用ServiceWorker,首先,需要通过serviceWorkerContainer.register()来进行注册,例如:

ServiceWorkerContainer.register("/test/service.js", {scope:"./"})
    .then(
        function(ServiceWorkerRegistration) {
            // do something
        }
);

假设当前域名为www.baidu.com/serviceWork,那么上面的方法就在www.baidu.com/serviceWork/test下面注册了一个ServiceWorker,假如把scope改成./hahaha,那么作用域就变成了www.baidu.com/serviceWork/test/hahaha,只要处于这个域名之下的所有页面,都受到这个ServiceWorker的控制。但是,假如将scope改成”../”,页面就会报出一个错误,因为这时候不允许设置比service.js所在位置层级更高的路径,除非添加一个Service-Worker-Allowedheader。

注册完成之后,受控界面会去安装serviceWorker,安装完成之后会处于等待状态,接下来有可能会进入激活状态,激活状态之后,页面还不一定是受控的,不过有相应的api可以控制这一系列流程。这些流程就是ServiceWorker最最复杂的生命周期了。

把ServiceWorkerModule导入到你的AppModule是不仅注册了
service worker,也提供了一些你可以使用的服务,可以作用到service
worker中并控制你app的缓存。

fetch事件

在页面发起http请求时,service
worker可以通过fetch事件拦截请求,并且给出自己的响应。
w3c提供了一个新的fetch
api,用于取代XMLHttpRequest,与XMLHttpRequest最大不同有两点:

1.
fetch()方法返回的是Promise对象,通过then方法进行连续调用,减少嵌套。ES6的Promise在成为标准之后,会越来越方便开发人员。

2. 提供了Request、Response对象,如果做过后端开发,对Request、Response应该比较熟悉。前端要发起请求可以通过url发起,也可以使用Request对象发起,而且Request可以复用。但是Response用在哪里呢?在service
worker出现之前,前端确实不会自己给自己发消息,但是有了service
worker,就可以在拦截请求之后根据需要发回自己的响应,对页面而言,这个普通的请求结果并没有区别,这是Response的一处应用。

下面是在中,作者利用fetch
api通过fliker的公开api获取图片的例子,注释中详细解释了每一步的作用:

JavaScript

/* 由于是get请求,直接把参数作为query string传递了 */ var URL =
”;
function fetchDemo() { // fetch(url,
option)支持两个参数,option中可以设置header、body、method信息
fetch(URL).then(function(response) { // 通过promise
对象获得相应内容,并且将响应内容按照json格式转成对象,json()方法调用之后返回的依然是promise对象
// 也可以把内容转化成arraybuffer、blob对象 return response.json();
}).then(function(json) { // 渲染页面 insertPhotos(json); }); }
fetchDemo();

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
/* 由于是get请求,直接把参数作为query string传递了 */
var URL = ‘https://api.flickr.com/services/rest/?method=flickr.photos.search&api_key=your_api_key&format=json&nojsoncallback=1&tags=penguins’;
 
function fetchDemo() {
  // fetch(url, option)支持两个参数,option中可以设置header、body、method信息
  fetch(URL).then(function(response) {
    // 通过promise 对象获得相应内容,并且将响应内容按照json格式转成对象,json()方法调用之后返回的依然是promise对象
    // 也可以把内容转化成arraybuffer、blob对象
    return response.json();
  }).then(function(json) {
    // 渲染页面
    insertPhotos(json);
  });
}
 
fetchDemo();

fetch
api与XMLHttpRequest相比,更加简洁,并且提供的功能更全面,资源获取方式比ajax更优雅。兼容性方面:chrome
42开始支持,对于旧浏览器,可以通过官方维护的polyfill支持。

Service Worker生命周期

service worker拥有一个完全独立于Web页面的生命周期

图片 3

sw-lifecycle.png

  1. 注册service worker,在网页上生效
  2. 安装成功,激活 或者 安装失败(下次加载会尝试重新安装)
  3. 激活后,在sw的作用域下作用所有的页面,首次控制sw不会生效,下次加载页面才会生效。
  4. sw作用页面后,处理fetch(网络请求)和message(页面消息)事件 或者
    被终止(节省内存)。

简单的例子

这是把express和sw-test简单结合的一个小demo, 项目运行起来访问
http://localhost:3000/sw-test/index.html
,
然后终止此服务依然能访问相应资源。Github

ServiceWorker生命周期


serviceWorker的生命周期有点复杂,情况很多,但是基本上符合一个理念,那就是渐进式。

前期准备

如下的基础知识:

  • [翻译]入门angular的 Service
    Workers.

message事件

页面和serviceWorker之间可以通过posetMessage()方法发送消息,发送的消息可以通过message事件接收到。

这是一个双向的过程,页面可以发消息给service worker,service
worker也可以发送消息给页面,由于这个特性,可以将service
worker作为中间纽带,使得一个域名或者子域名下的多个页面可以自由通信。

这里是一个小的页面之间通信demo

Service Worker支持使用

相关代码

  • /public/sw-test/app.js
  1. 首先判断了浏览器是否支持
  2. 调用 register 方法注册 service worker, 第一个参数是运行 service
    worker 的
    js 文件, 第二个 scope 参数是选填的,可以被用来指定你想让 service
    worker 控制的内容的子目录。 在这个例子,我们指定了 ‘/sw-test/’,即
    http://localhost:3000/sw-test/
    下的请求会被捕获, 被指定的资源会被缓存。
  3. register 方法返回一个 Promise , 进行正确错误处理。

  if ('serviceWorker' in navigator) {
  navigator.serviceWorker.register('/sw-test/sw.js', { scope: '/sw-test/' }).then(function(reg) {
    // registration worked
    console.log('Registration succeeded. Scope is ' + reg.scope);
  }).catch(function(error) {
    // registration failed
    console.log('Registration failed with ' + error);
  });
}
  • /public/sw-test/sw.js
    核心文件,监听安装事件, 打开缓存 v1 增加需要缓存资源 request url
    list, 截取被控文件下请求, 如果不存在该缓存则进行缓存处理
  1. 监听了 install 事件, event.waitUntil 主要用在 Install, activate
    事件中,
    在服务工作线程中,延长事件的寿命从而阻止浏览器在事件中的异步操作完成之前终止服务工作线程。
  2. Cache 接口提供缓存的
    Request,
    Response
    对象对的存储机制,例如作为ServiceWorker生命周期的一部分。
    Cache 接口像 workers 一样, 是暴露在 window
    作用域下的。尽管它被定义在 service worker 的标准中,
    但是它不必一定要配合 service worker
    使用.Cache详细API
  3. event.respondWith
    方法旨在包裹代码,这些代码为来自受控页面的request生成自定义的response,查看更多。response.clone()
    创建了一个响应对象的克隆,这个对象在所有方面都是相同的,但是存储在一个不同的变量中。防止多次使用篡改了对象。

self.addEventListener('install', function(event) {
  event.waitUntil(
    caches.open('v1').then(function(cache) {
      return cache.addAll([
        '/sw-test/',
        '/sw-test/index.html',
        '/sw-test/style.css',
        '/sw-test/app.js',
        '/sw-test/image-list.js',
        '/sw-test/star-wars-logo.jpg',
        '/sw-test/gallery/bountyHunters.jpg',
        '/sw-test/gallery/myLittleVader.jpg',
        '/sw-test/gallery/snowTroopers.jpg'
      ]);
    })
  );
});

self.addEventListener('fetch', function(event) {
  event.respondWith(caches.match(event.request).then(function(response) {
    // caches.match() always resolves
    // but in case of success response will have value
    if (response !== undefined) {
      return response;
    } else {
      return fetch(event.request).then(function (response) {
        // response may be used only once
        // we need to save clone to put one copy in cache
        // and serve second one
        let responseClone = response.clone();

        caches.open('v1').then(function (cache) {
          cache.put(event.request, responseClone);
        });
        return response;
      }).catch(function () {
        return caches.match('/sw-test/gallery/myLittleVader.jpg');
      });
    }
  }));
});

install

执行完注册之后,浏览器会去下载,假如脚本没有错误的话,就会进行安装,安装会在Worker内触发install事件,安装分为两个状态:installinginstalled,这里可以执行一些操作。

self.addEventListener("install",installEvent=>{
    self.skipWaiting();//跳过waiting
    installEvent.waitUntil(
        caches.open(CACHE_NAME).then(cache=>{
            return cache.add("http://upload.jianshu.io/users/upload_avatars/9545112/f186bd4a-1da4-4912-b926-a744bc128d06.png?imageMogr2/auto-orient/strip|imageView2/1/w/240/h/240");
        })
    )

});

这里会有一个installEvent,通过waitUtil方法可以阻塞安装状态,这个方法可以传入一个promise,只有这个promise正确resolve之后,才会完成安装,同时,还有一个skipwaiting方法,这个方法可以跳过installed和activating状态之间的waiting状态。
首次打开页面会安装work,然后接下来再检测到新的work也会再次执行安装。
触发更新的几种情况:

  • 第一次导航到作用域范围内页面的时候
  • 当在24小时内没有进行更新检测并且触发功能性时间如push或sync的时候
  • SW 的 URL 发生变化并调用.register()时
  • 手动执行reg.update()
  • 重载页面(有些情况下不会更新,原因不明)

更新会比对旧版本和新版本的字节,假如字节不一致,则更新。在更新install完成之后,会进入waiting状态,直到旧版本不控制任何client(受控的页面)再进入activating状态。

SwUpdate 服务

SwUpdate服务能让你访问到一些事件,如service worker发现了可用的新的更新
或 service worker更新到了最新版。
The [SwUpdate](https://angular.io/api/service-worker/SwUpdate) service
supports four separate operations:
SwUpdate服务支持4个独立的方法:

  • 获得可用更新的信息的观察对象。这是那些新版本文件的信息,页面刷新时就会启动到。
  • 获得更新成功的观察对象。service worker启用新版本时会发布。
  • 要求service worker去检查更新。
  • 要求service worker在当前页面启动最新版本的app。

利用service workder缓存文件

下面介绍一个利用service worker缓存离线文件的例子
准备index.js,用于注册service-worker

JavaScript

if (navigator.serviceWorker) {
navigator.serviceWorker.register(‘service-worker.js’).then(function(registration)
{ console.log(‘service worker 注册成功’); }).catch(function (err) {
console.log(‘servcie worker 注册失败’) }); }

1
2
3
4
5
6
7
if (navigator.serviceWorker) {
    navigator.serviceWorker.register(‘service-worker.js’).then(function(registration) {
        console.log(‘service worker 注册成功’);
    }).catch(function (err) {
        console.log(‘servcie worker 注册失败’)
    });
}

在上述代码中,注册了service-worker.js作为当前路径下的service
worker。由于service
worker的权限很高,所有的代码都需要是安全可靠的,所以只有https站点才可以使用service
worker,当然localhost是一个特例。
注册完毕,现在开始写service-worker.js代码。
根据前面的生命周期图,在一个新的service
worker被注册以后,首先会触发install事件,在service-workder.js中,可以通过监听install事件进行一些初始化工作,或者什么也不做。
因为我们是要缓存离线文件,所以可以在install事件中开始缓存,但是只是将文件加到caches缓存中,真正想让浏览器使用缓存文件需要在fetch事件中拦截

JavaScript

var cacheFiles = [ ‘about.js’, ‘blog.js’ ];
self.addEventListener(‘install’, function (evt) { evt.waitUntil(
caches.open(‘my-test-cahce-v1’).then(function (cache) { return
cache.addAll(cacheFiles); }) ); });

1
2
3
4
5
6
7
8
9
10
11
var cacheFiles = [
    ‘about.js’,
    ‘blog.js’
];
self.addEventListener(‘install’, function (evt) {
    evt.waitUntil(
        caches.open(‘my-test-cahce-v1’).then(function (cache) {
            return cache.addAll(cacheFiles);
        })
    );
});

首先定义了需要缓存的文件数组cacheFile,然后在install事件中,缓存这些文件。
evt是一个InstallEvent对象,继承自ExtendableEvent,其中的waitUntil()方法接收一个promise对象,直到这个promise对象成功resolve之后,才会继续运行service-worker.js。
caches是一个CacheStorage对象,使用open()方法打开一个缓存,缓存通过名称进行区分。
获得cache实例之后,调用addAll()方法缓存文件。

这样就将文件添加到caches缓存中了,想让浏览器使用缓存,还需要拦截fetch事件

JavaScript

// 缓存图片 self.addEventListener(‘fetch’, function (evt) {
evt.respondWith( caches.match(evt.request).then(function(response) { if
(response) { return response; } var request = evt.request.clone();
return fetch(request).then(function (response) { if (!response &&
response.status !== 200 &&
!response.headers.get(‘Content-type’).match(/image/)) { return response;
} var responseClone = response.clone();
caches.open(‘my-test-cache-v1’).then(function (cache) {
cache.put(evt.request, responseClone); }); return response; }); }) ) });

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
// 缓存图片
self.addEventListener(‘fetch’, function (evt) {
    evt.respondWith(
        caches.match(evt.request).then(function(response) {
            if (response) {
                return response;
            }
            var request = evt.request.clone();
            return fetch(request).then(function (response) {
                if (!response && response.status !== 200 && !response.headers.get(‘Content-type’).match(/image/)) {
                    return response;
                }
                var responseClone = response.clone();
                caches.open(‘my-test-cache-v1’).then(function (cache) {
                    cache.put(evt.request, responseClone);
                });
                return response;
            });
        })
    )
});

通过监听fetch事件,service worker可以返回自己的响应。

首先检缓存中是否已经缓存了这个请求,如果有,就直接返回响应,就减少了一次网络请求。否则由service
workder发起请求,这时的service workder起到了一个中间代理的作用。

service worker请求的过程通过fetch
api完成,得到response对象以后进行过滤,查看是否是图片文件,如果不是,就直接返回请求,不会缓存。

如果是图片,要先复制一份response,原因是request或者response对象属于stream,只能使用一次,之后一份存入缓存,另一份发送给页面。
这就是service worker的强大之处:拦截请求,伪造响应。fetch
api在这里也起到了很大的作用。

 

service
worker的更新很简单,只要service-worker.js的文件内容有更新,就会使用新的脚本。但是有一点要注意:旧缓存文件的清除、新文件的缓存要在activate事件中进行,因为可能旧的页面还在使用之前的缓存文件,清除之后会失去作用。

 

在初次使用service worker的过程中,也遇到了一些问题,下面是其中两个

浏览器支持

service worker
support

图片 4

navigator-serviceworker.png

版本更新删除旧缓存

  1. 监听 activate 事件, 如当前版本 v2,删除与当前不匹配缓存数据。

this.addEventListener('activate', function(event) {
  var cacheWhitelist = ['v2'];

  event.waitUntil(
    caches.keys().then(function(keyList) {
      return Promise.all(keyList.map(function(key) {
        if (cacheWhitelist.indexOf(key) === -1) {
          return caches.delete(key);
        }
      }));
    })
  );
});

activate

activate的状态比测试妹子的思考逻辑还难猜测。但是这个遵循一个原则,只有所有的页面都不受老的serviceWorker控制的时候,才会开始激活。意思就是所有worker作用的页面都关了,相当于重启更新,再次启动的时候,才会进入激活状态。激活也会触发一个activate状态.

self.addEventListener("activate", ExtendableEvent => {
  ExtendableEvent.waitUntil(self.clients.claim());
 }

activate也和install一样,也有waitUtil方法,效果也是一样的。激活完成之后其实还不一定有效果,因为页面可能还处于不受控的状态,或者有另外一个页面,没有刷新的,但是这个时候新开一个页面更新了work,这个时候就需要调用clients.claim操作来让所有打开的页面受控。但是…work的执行是异步的,也就是说,页面在执行这个方法之前是不受控,或者是不是受最新的work控制的,在利用work来缓存的时候,就会出现问题,导致版本不一致,所以,个人感觉serviceWorker比较适合做一个辅助者,没有也行,有也行,可以通过设置一个button之类的来提醒用户,发现新版本,是否需要刷新之类的操作。
下面这张图很好的概括了生命周期

图片 5

生命周期

可用和启用中的更新

这两个更新事件,available and
activated,是SwUpdate的观察者对象

@Injectable()
export class LogUpdateService {

  constructor(updates: SwUpdate) {
    updates.available.subscribe(event => {
      console.log('current version is', event.current);
      console.log('available version is', event.available);
    });
    updates.activated.subscribe(event => {
      console.log('old version was', event.previous);
      console.log('new version is', event.current);
    });
  }
}

你可以使用这个事件去通知用户有一个待用更新或去刷新页面去使用新更新。

问题1. 运行时间

service
worker并不是一直在后台运行的。在页面关闭后,浏览器可以继续保持service
worker运行,也可以关闭service
worker,这取决与浏览器自己的行为。所以不要定义一些全局变量,例如下面的代码(来自):

JavaScript

var hitCounter = 0; this.addEventListener(‘fetch’, function(event) {
hitCounter++; event.respondWith( new Response(‘Hit number ‘ +
hitCounter) ); });

1
2
3
4
5
6
7
8
var hitCounter = 0;
 
this.addEventListener(‘fetch’, function(event) {
  hitCounter++;
  event.respondWith(
    new Response(‘Hit number ‘ + hitCounter)
  );
});

返回的结果可能是没有规律的:1,2,1,2,1,1,2….,原因是hitCounter并没有一直存在,如果浏览器关闭了它,下次启动的时候hitCounter就赋值为0了
这样的事情导致调试代码困难,当你更新一个service
worker以后,只有在打开新页面以后才可能使用新的service
worker,在调试过程中经常等上一两分钟才会使用新的,比较抓狂。

polyfill

使用ServiceWorker cache
polyfill让旧版本浏览器支持
ServiceWorker cache API,

参照 MDN

Service Worker
详细文档

Fetch

激活完成之后,work内可以拦截到fetch事件,通过这个,就可以执行缓存操作。fetch事件回调会返回一个fetchEvent对象,通过fetchEvent.respondWith()可以自定义返回的response。

检查更新

可以要求service
worker去检查服务器是否部署了任何更新。如果你的网站经常改变或者有一个更新计划,你就可以这样去执行检查。

通过使用checkForUpdate()方法:

import { interval } from 'rxjs/observable/interval';

@Injectable()
export class CheckForUpdateService {

  constructor(updates: SwUpdate) {
    interval(6 * 60 * 60).subscribe(() => updates.checkForUpdate());
  }
}

这方法返回一个Promise对象,表示更新检查成功,但是它不能说明是否有新更新。因为即使有一个更新被发现了,service
worker也必须成功下载被修复过的文件,这是有可能失败的。如果下载也成功了,可用更新的available观察者就能订阅到新版本的信息。

问题2. 权限太大

当service worker监听fetch事件以后,对应的请求都会经过service
worker。通过chrome的network工具,可以看到此类请求会标注:from service
worker。如果service
worker中出现了问题,会导致所有请求失败,包括普通的html文件。所以service
worker的代码质量、容错性一定要很好才能保证web app正常运行。

 

参考文章:

1. 

2. 

3. 

4. 

5. 

1 赞 3 收藏
评论

图片 6

https

Server需要支持https

通过service
worker可以劫持连接,伪造和过滤响应,为了避免这些问题,只能在HTTPS的网页上注册service
workers,防止加载service worker的时候不被坏人篡改。

Github Pages是HTTPS的,可以通过Github做一些尝试

其他事件

….

强制更新

如果当前页面需要马上更新到最新版本,可以使用activateUpdate()方法:

@Injectable()
export class PromptUpdateService {

  constructor(updates: SwUpdate) {
    updates.available.subscribe(event => {
      if (promptUser(event)) {
        updates.activateUpdate().then(() => document.location.reload());
      }
    });
  }
}

这么做就能破坏当前app的懒加载,特别是那些每次修改都会更改名字的懒加载文件。

调试工具

在调试的时候可以用于unregister、stop、start等操作

chrome访问chrome://inspect/#service-workerschrome://serviceworker-internals查看service-workers

图片 7

chrome-tool-1.png

图片 8

chrome-tool-2.png

firefox通过about:debugging#workers查看

图片 9

firefox-tool.png

更多请参考debugging-service-workers

杂谈


ServiceWorker的大多数功能都要在worker线程中使用,在页面中,通过注册返回的ServiceWorkerRegistration可以获取很多有用的信息,例如正在安装的worker,等待中的worker,激活的worker,worker作用域等。同时,可以监听状态的变换。例如,

ServiceWorkerRegistration.addEventListener("updatefound",()=>{
            const netWorker=ServiceWorkerRegistration.installing;
            console.log(`state:${netWorker.state}`);
            netWorker.addEventListener("statechange", () => {
              // newWorker 状态发生变化
              console.log(`state变换:${netWorker.state}`)
              if(netWorker.state=="installed"){
                console.log("检测到新版本!");
              }
            });
        })

这里,先获取正在安装中的worker,然后监听状态变换,当切换成installed状态时,假如之前已经存在serviceWorker,就可以判定新安装的worker是更新进去的,这个时候就有可能部分资源是通过老的worker缓存请求的,部分资源在worker受控之后是通过worker缓存请求的,这样就存在不一致的问题,所以这时候就可以提醒用户有新版本更新,从而刷新更新。假如之前没有worker,那么就说明这个worker是第一次注册安装,因为是第一次,所以没有所谓的缓存资源版本不一致的问题,没必要提醒用户更新。
针对serviceWorker机制所导致的各种资源同步问题,将其想像成渐进式的就轻松多了,将worker当作一个辅助者,把缓存想象成可有可无的。进一步来看可以不使用skipWaiting,只有在用户重启的时候才可以更新,这时候所有的资源就不存在不同步的问题了,所有的请求都会在重启更新之后直接进入worker。

更多的关于 Angular service workers

你可能对以下感兴趣

  • 生产环境中的Service
    Worker.

你们的赞赏是我的无限动力

离线存储数据

对URL寻址资源,使用Cache
API。对其他数据,使用IndexedDB。

离线阅读

demo

使用https访问本文,打开ChromeDevTools,选择Application选项卡->Service
Workers

图片 10

sw-devtools.png

可以看到Service Workers注册

点击下面离线保存按钮

<button class=”btn btn-sm btn-primary
offline-btn”>离线保存</button>

然后选择Cache Storage,可以看到文字内容已经缓存到Cache Storage

图片 11

sw-cache.png

然后选择Service Workers 勾选
Offline,NetWork出现了⚠️️,然后试试离线访问本文😎

图片 12

sw-offline.png

原理

注册 service worker

创建一个 JavaScript 文件(比如:sw.js)作为 service worker

告诉浏览器注册这个JavaScript文件为service worker,检查service worker
API是否可用,如果可用就注册service worker

if ('serviceWorker' in navigator) {
  navigator.serviceWorker.register('/sw.js').then(function(registration) {
    // Registration was successful
    console.log('ServiceWorker registration successful with scope: ',    registration.scope);
  }).catch(function(err) {
    // registration failed :(
    console.log('ServiceWorker registration failed: ', err);
  });
}

sw.js文件被放在这个域的根目录下,和网站同源。这个service
work将会收到这个域下的所有fetch事件。

如果将service worker文件注册为/example/sw.js,那么,service
worker只能收到/example/路径下的fetch事件(例如: /example/page1/,
/example/page2/)。

// Service Workers
if ('serviceWorker' in navigator && navigator.onLine) {
  navigator.serviceWorker.register('/sw.js').then(function(registration) {
    console.log('ServiceWorker registration successful with scope: ',    registration.scope);
  }).catch(function(err) {
    console.log('ServiceWorker registration failed: ', err);
  });

  var currentPath = window.location.pathname;
  var cacheButton = document.querySelector('.offline-btn');

  var imageArray = document.querySelectorAll('img');
  if(cacheButton) {
    cacheButton.addEventListener('click', function(event) {
      event.preventDefault();
      // 缓存当前链接和使用的图片
      var pageResources = [currentPath];
      for (i = 0; i < imageArray.length; i++) {
        pageResources.push(imageArray[i].src);
      }
      caches.open('offline-' + currentPath).then(function(cache) {
        var updateCache = cache.addAll(pageResources);

        updateCache.then(function() {
          console.log('Article is now available offline.');
          cacheButton.innerHTML = "☺";
        });

        updateCache.catch(function (error) {
          console.log('Article could not be saved offline.');
          cacheButton.innerHTML = "☹";
        });
      });
    });
  }
}

缓存站点的资源

定义需要缓存的文件,然后在sw注册安装后回到cache
Api将资源文件写入缓存。如果所有的文件都被缓存成功了,那么service
worker就安装成功了。如果任何一个文件下载失败,那么安装步骤就会失败。

var cacheName = 'v1';
var assetsToCache = [
  '/styles/main.css',
  '/script/main.js'
];

self.addEventListener('install', function(event) {
  event.waitUntil(
    caches.open(cacheName).then(function(cache) {
      return cache.addAll(assetsToCache);
    }).then(function() {
      return self.skipWaiting();
    })
  );
});

从缓存中加载

service
worker成功注册,并且用户浏览了另一个页面或者刷新了当前的页面,service
worker将开始接收到fetch事件。

拦截网络请求并使用缓存,缓存命中,返回缓存资源,否则返回一个实时从网络请求fetch的结果。

self.addEventListener('fetch', function(event) {
  var requestUrl = new URL(event.request.url);
  if (requestUrl.origin === location.origin) {
      if (requestUrl.pathname === '/') {
      event.respondWith(
        caches.open(cacheName).then(function(cache) {
          return fetch(event.request).then(function(networkResponse) {
            cache.put(event.request, networkResponse.clone());
            return networkResponse;
          }).catch(function() {
            return cache.match(event.request);
          });
        })
      );
    }
  }

  event.respondWith(
    caches.match(event.request).then(function(response) {
      return response || fetch(event.request);
    })
  );
});

缓存版本管理

版本修改的时候会触发activate,将旧版本的缓存清理掉。

var OFFLINE_PREFIX = 'offline-';
var CACHE_NAME = 'main_v1.0.0';
self.addEventListener('activate', function(event) {
  var mainCache = [CACHE_NAME];
  event.waitUntil(
    caches.keys().then(function(cacheNames) {
      return Promise.all(
        cacheNames.map(function(cacheName) {
          if ( mainCache.indexOf(cacheName) === -1 && cacheName.indexOf(OFFLINE_PREFIX) === -1 ) {
            // When it doesn't match any condition, delete it.
            console.info('SW: deleting ' + cacheName);
            return caches.delete(cacheName);
          }
        })
      );
    })
  );
  return self.clients.claim();
});

Service Worker 库

  • sw-toolbox
  • sw-precache
  • offline-plugin,webpack离线插件

参考

  • your-first-pwapp
  • service-workers
  • 渐进式 Web App
    的离线存储

发表评论

电子邮件地址不会被公开。 必填项已用*标注