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是如何工作的。

连不上网?英国卫报的个性离线页面是这样做的

2015/11/20 · HTML5 · Service
Worker
,
离线页面

本文由 伯乐在线
Erucy
翻译,weavewillg
校稿。未经许可,禁止转载!
英文出处:Oliver
Ash
。欢迎加入翻译组

我们是如何使用 service worker 来为 theguardian.com
构建一个自定义的离线页面。

正规网赌平台 1

theguardian.com 的离线页面。插图:Oliver Ash

你正在通往公司路上的地铁里,在手机上打开了
Guardian
应用。地铁被隧道包围着,不过这个应用可以如常运行,即使没有网络连接,你也能获得完整的功能,除了显示的内容可能有点旧。如果你尝试在网站上也这么干,可惜它完全没法加载:

正规网赌平台 2

安卓版 Chrome 的离线页面

Chrome 中的这个彩蛋,很多人都不知道

Chrome
在离线页面上有个隐藏的游戏(桌面版上按空格键,手机版上点击那只恐龙),这多少能减轻一点你的烦躁。不过我们可以做得更好。

Service
workers

允许网站作者拦截自己站点的所有网络请求,这也就意味着我们可以提供完善的离线体验,就像原生应用一样。在
Guardian
网站,我们最近上线了一个自定义的离线体验功能。当用户离线的时候,他们会看到一个带有
Guardian
标识的页面,上面带有一个简短的离线提示,还有一个填字游戏,他们可以在等待网络连接的时候玩玩这个找点乐子。这篇博客解释了我们是如何构建它的,不过在开始之前,你可以先自己试试看。

生命周期

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

正规网赌平台 3
上图是service
worker生命周期,出处http://www.html5rocks.com/en/tutorials/service-worker/introduction/

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

  1.  安装

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

正规网赌平台 4

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

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

试试看

你需要一个支持 Service
Worker
fetch
API
的浏览器。截止到本文编写时只有
Chrome(手机版和桌面版)同时支持这两种 API(译者注:Opera
目前也支持这两者),不过 Firefox
很快就要支持了(在每日更新的版本中已经支持了),除了 Safari
之外的所有浏览器也都在跃跃欲试
。此外,service worker 只能注册在使用了
HTTPS 的网站上,theguardian.com
已经开始逐步迁移到 HTTPS,所以我们只能在网站的 HTTPS
部分提供离线体验。就目前来说,我们选择了 开发者博客 作为我们用来测试的地方。所以如果你是在我们网站的 开发者博客 部分阅读这篇文章的话,很走运。

当你使用支持的浏览器访问我们的 开发者博客 中的页面的时候,一切就准备妥当了。断开你的网络连接,然后刷新一下页面。如果你自己没条件尝试的话,可以看一下这段 演示视频(译者注:需梯子)。

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的一处应用。

下面是在http://www.sitepoint.com/introduction-to-the-fetch-api/中,作者利用fetch
api通过fliker的公开api获取图片的例子,注释中详细解释了每一步的作用:

JavaScript

/* 由于是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();

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支持。

工作原理

通过一段简单的
JavaScript,我们可以指示浏览器在用户访问页面的时候立即注册我们自己的
service worker。目前支持 service worker
的浏览器很少,所以为了避免错误,我们需要使用特性检测。

JavaScript

if (navigator.serviceWorker) {
navigator.serviceWorker.register(‘/service-worker.js’); }

1
2
3
if (navigator.serviceWorker) {
    navigator.serviceWorker.register(‘/service-worker.js’);
}

Service worker
安装事件的一部分,我们可以使用 新的缓存
API
 来缓存我们网站中的各种内容,比如
HTMLCSS
JavaScript

JavaScript

var staticCacheName = ‘static’; var version = 1; function updateCache()
{ return caches.open(staticCacheName + version) .then(function (cache) {
return cache.addAll([ ‘/offline-page.html’, ‘/assets/css/main.css’,
‘/assets/js/main.js’ ]); }); }; self.addEventListener(‘install’,
function (event) { event.waitUntil(updateCache()); });

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
var staticCacheName = ‘static’;
var version = 1;
 
function updateCache() {
    return caches.open(staticCacheName + version)
        .then(function (cache) {
            return cache.addAll([
                ‘/offline-page.html’,
                ‘/assets/css/main.css’,
                ‘/assets/js/main.js’
            ]);
        });
};
 
self.addEventListener(‘install’, function (event) {
    event.waitUntil(updateCache());
});

当安装完成后,service worker
可以监听和控制 fetch
事件,让我们可以完全控制之后网站中产生的所有网络请求。

JavaScript

self.addEventListener(‘fetch’, function (event) {
event.respondWith(fetch(event.request)); });

1
2
3
self.addEventListener(‘fetch’, function (event) {
    event.respondWith(fetch(event.request));
});

在这里我们有很灵活的空间可以发挥,比如下面这个点子,可以通过代码来生成我们自己的请求响应:

JavaScript

self.addEventListener(‘fetch’, function (event) { var response = new
Response(‘<h1>Hello, World!</h1>’, { headers: {
‘Content-Type’: ‘text/html’ } }); event.respondWith(response); });

1
2
3
4
5
self.addEventListener(‘fetch’, function (event) {
    var response = new Response(‘&lt;h1&gt;Hello, World!&lt;/h1&gt;’,
        { headers: { ‘Content-Type’: ‘text/html’ } });
    event.respondWith(response);
});

还有这个,如果在缓存中找到了请求相应的缓存,我们可以直接从缓存中返回它,如果没找到的话,再通过网络获取响应内容:

JavaScript

self.addEventListener(‘fetch’, function (event) { event.respondWith(
caches.match(event.request) .then(function (response) { return response
|| fetch(event.request); }) ); });

1
2
3
4
5
6
7
8
self.addEventListener(‘fetch’, function (event) {
    event.respondWith(
        caches.match(event.request)
            .then(function (response) {
                return response || fetch(event.request);
            })
    );
});

那么我们如何使用这些功能来提供离线体验呢?

首先,在 service worker
安装过程中,我们需要把离线页面需要的 HTML 和资源文件通过 service worker
缓存下来。在缓存中,我们加载了自己开发的 填字游戏 的
React应用 页面。之后,我们会拦截所有访问
theguardian.com
网络请求,包括网页、以及页面中的资源文件。处理这些请求的逻辑大致如下:

  1. 当我们检测到传入请求是指向我们的 HTML
    页面时,我们总是会想要提供最新的内容,所以我们会尝试把这个请求通过网络发送给服务器。

    1. 当我们从服务器得到了响应,就可以直接返回这个响应。
    2. 如果网络请求抛出了异常(比如因为用户掉线了),我们捕获这个异常,然后使用缓存的离线
      HTML 页面作为响应内容。
  2. 否则,当我们检测到请求的不是 HTML
    的话,我们会从缓存中查找响应的请求内容。

    1. 如果找到了缓存内容,我们可以直接返回缓存的内容。
    2. 否则,我们会尝试把这个请求通过网络发送给服务器。

在代码中,我们使用了 新的缓存
API
(它是 Service Worker API 的一部分)以及
fetch
功能(用于生成网络请求),如下所示:

JavaScript

var doesRequestAcceptHtml = function (request) { return
request.headers.get(‘Accept’) .split(‘,’) .some(function (type) { return
type === ‘text/html’; }); }; self.addEventListener(‘fetch’, function
(event) { var request = event.request; if
(doesRequestAcceptHtml(request)) { // HTML pages fallback to offline
page event.respondWith( fetch(request) .catch(function () { return
caches.match(‘/offline-page.html’); }) ); } else { // Default fetch
behaviour // Cache first for all other requests event.respondWith(
caches.match(request) .then(function (response) { return response ||
fetch(request); }) ); } });

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
var doesRequestAcceptHtml = function (request) {
    return request.headers.get(‘Accept’)
        .split(‘,’)
        .some(function (type) { return type === ‘text/html’; });
};
 
self.addEventListener(‘fetch’, function (event) {
    var request = event.request;
    if (doesRequestAcceptHtml(request)) {
        // HTML pages fallback to offline page
        event.respondWith(
            fetch(request)
                .catch(function () {
                    return caches.match(‘/offline-page.html’);
                })
        );
    } else {
        // Default fetch behaviour
        // Cache first for all other requests
        event.respondWith(
            caches.match(request)
                .then(function (response) {
                    return response || fetch(request);
                })
        );
    }
});

就只需要这么多!theguardian.com
上的 所有代码都是在 GitHub
上开源
 的,所以你可以去那儿查看我们的
service worker
的完整版本
,或者直接从生产环境上访问
https://www.theguardian.com/service-worker.js

我们有充足的理由为这些新的浏览器技术欢呼喝彩,因为它可以用来让你的网站像今天的原生应用一样,拥有完善的离线体验。未来当
theguardian.com 完全迁移到 HTTPS
之后,离线页面的重要性会明显增加,我们可以提供更加完善的离线体验。设想一下你在上下班路上网络很差的时候访问
theguardian.com,你会看到专门为你订制的个性化内容,它们是在你之前访问网站时由浏览器缓存下来的。它在安装过程中也不会产生任何不便,你所需要的只是访问这个网站而已,不像原生应用,还需要用户有一个应用商店的账号才能安装。Service
worker
同样可以帮助我们提升网站的加载速度,因为网站的框架能够被可靠地缓存下来,就像原生应用一样。

如果你对 service worker
很感兴趣,想要了解更多内容的话,开发者 Matt
Gaunt(Chrome的忠实支持者)写了一篇更加详细地 介绍 Service
Worker
的文章。

打赏支持我翻译更多好文章,谢谢!


打赏译者

message事件

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

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

这里是一个小的页面之间通信demohttps://nzv3tos3n.qnssl.com/message/msg-demo.html

打赏支持我翻译更多好文章,谢谢!

正规网赌平台 5

1 赞 收藏
评论

相关文章