service worker 介绍

service worker 是运行在浏览器后台的 JS 程序,可以操控浏览器端的存储、网络请求、消息推送等。特点:

  • 基于 web worker(一个独立于JS主线程的线程,在里面执行耗时操作不会堵塞主线程)
  • 在 web worker 的基础上增加了离线缓存的能力;
  • 本质上充当服务器与浏览器之间的代理服务器,可以拦截全站的请求并响应;
  • 由事件驱动,具有生命周期,包含 register / install / activate 等阶段;
  • 只能在 https 域 或者 localhost 下使用;
  • 支持降级处理,客户端不支持 service worker 时,对流程没有任何影响;

1. 使用步骤

把 service worker 看作一个代理服务器,通过例子直观的感受它。

1.1 注册 service worker 任务

if ('serviceWorker' in navigator) {
  window.addEventListener('load', function () {
    navigator.serviceWorker.register('sw.js').then(function (registration) {
      console.log('ServiceWorker registration successful with scope: ', registration.scope);
    }, function (err) {
      console.log('ServiceWorker registration failed: ', err);
    });
  });
}

1.2 处理安装逻辑

// 监听安装事件
// install 事件一般是被用来设置你的浏览器的离线缓存逻辑
this.addEventListener("install", function (event) {
  // event.waitUntil(Promise) 保证缓存动作完成后,才会进入 installed 状态
  // 该方法会等待 Promise resolved
  event.waitUntil(
    // 打开 key 为 pwa-static 的缓存
    caches.open("pwa-static").then(function (cache) {
            // cache.addAll 指定要缓存的内容,地址为相对于跟域名的访问路径
      return cache.addAll(["./index.html"]);
    })
  );
});

监听 install 事件,当 install 事件触发时,进入 installing 阶段,使用 event.waitUntil() 保证 index.html 被缓存之后才进入 installed 状态。caches.open 用于打开一个缓存,cache.addAll 用于添加需要缓存的文件,参数为url列表。如果 event.waitUntil 接收的 promise 发生错误,则会导致 install 失败。比如添加一个不存在的文件地址。

1.3 监听 fetch 事件

// 注册fetch事件, 拦截请求, 可以拦截所有请求
this.addEventListener('fetch', function(event) {
  // 在缓存中匹配对应请求资源直接返回
  event.respondWith(caches.match(event.request));
});

在 service worker 中可以监听任何 http 请求,定义响应策略,上面的代码中表示从缓存中读取并响应。

1.4 一个完整的例子

// register service worker
if ('serviceWorker' in navigator) {
  navigator.serviceWorker.register('/sw-test/sw.js', { scope: '/sw-test/' }).then(function(reg) {

    if(reg.installing) {
      console.log('Service worker installing');
    } else if(reg.waiting) {
      console.log('Service worker installed');
    } else if(reg.active) {
      console.log('Service worker active');
    }

  }).catch(function(error) {
    // registration failed
    console.log('Registration failed with ' + error);
  });
}
const version = 'v7';

self.addEventListener('install', function(event) {
  event.waitUntil(
    caches.open(version).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'
      ]);
    }).then(() => {
      return self.skipWaiting()
    })
  );
});

// 清理过期缓存
function clearCache() {
  console.log("clearCacheclearCacheclearCacheclearCache")
  return caches.keys().then((keys) => {
    return Promise.all(
      keys.map((key) => {
        console.log(key, version)
        if (key !== version) {
          return caches.delete(key);
        }
      })
    );
  });
}

// 激活事件
self.addEventListener("activate", (event) => {
  console.log("activate");
  const list = Promise.all([clearCache(), self.clients.claim()])
  event.waitUntil(list);
});

self.addEventListener('fetch', function(event) {
  event.respondWith(caches.match(event.request).then(function(response) {
    if (response !== undefined) {
      return response;
    } else {
      return fetch(event.request).then(function (response) {
        let responseClone = response.clone();
        return caches.open(version).then(function (cache) {
          cache.put(event.request, responseClone);
          return response;
        })
      }).catch(function (err) {
        console.log(err, 'err')
        return caches.match('/sw-test/gallery/myLittleVader.jpg');
      });
    }
  }));
});

2. API 介绍

2.1 Cache API

service worker 中 self.caches 暴露了访问 cache 的能力,详情见 https://developer.mozilla.org/zh-CN/docs/Web/API/Cache

// service-worker.js
// self.caches
// self.caches.open(key: string)
// caches.keys()
// caches.delete(key: string);
Cache.match(request, options)
// 返回一个 Promise对象,resolve的结果是跟 Cache 对象匹配的第一个已经缓存的请求,找不到返回 undefined;

Cache.add(request)
// 抓取这个URL, 检索并把返回的 response 对象添加到给定的 Cache 对象。这在功能上等同于调用 fetch(), 然后使用 Cache.put() 将 response 添加到 cache 中;

Cache.addAll(requests)
// 抓取一个URL数组,检索并把返回的response对象添加到给定的Cache对象;

Cache.put(request, response)
// 同时抓取一个请求及其响应,并将其添加到给定的cache。

Cache.delete(request, options)
// 搜索key值为request的Cache 条目。如果找到,则删除该Cache 条目,并且返回一个resolve为true的Promise对象;如果未找到,则返回一个resolve为false的Promise对象。

Cache.keys(request, options)
// 返回一个Promise对象,resolve的结果是Cache对象key值组成的数组。

2.2 event.waitUntil()

查看详细文档,这个方法告诉浏览器事件一直进行,直至 promise 解决,浏览器不应该在事件中的异步操作完成之前终止服务工作线程。

  • install 事件使用 waitUntil() 来将服务工作线程保持在 installing 阶段。如果传入 waitUntil() 的 promise 被拒绝,则将此次安装视为失败,丢弃这个服务工作线程。这主要用于确保在服务工作线程安装以前,所有依赖的核心缓存都已经成功载入。
  • activate 事件使用 waitUntil() 来延迟函数事件,如 fetch 和 push,直至传入 waitUntil() 的 promise 被解决。这让服务工作线程有时间更新数据库和删除过时缓存,让其他事件能在一个完成更新的状态下进行。

2.3 self.skipWaiting()

当安装成功后,更新的 Worker会处于等待状态,直到现有 Worker 让出授权,另外在刷新期间客户端会重叠。
使用 self.skipWaiting() 会跳过等待,Service Worker 在安装完后立即激活。

3. 注意事项

  • 非 PWA 应用,客户端不要缓存 html 文件,否则更会非常的麻烦;
  • service worker 文件本身的更新,依赖 HTTP 缓存策略;
  • service worker 文件本身,服务端最好设置 cache-control: no-store 不要缓存;
  • 多次 register() 同一个 Service Worker 不会触发更新

4. 参考文档

  1. 使用 Service Workers [ https://developer.mozilla.org/zh-CN/docs/Web/API/Service_Worker_API/Using_Service_Workers#service_workers_demo]
  2. Service Worker: 简介 [https://developers.google.cn/web/fundamentals/primers/service-workers/]
  3. MDN Cache [https://developer.mozilla.org/zh-CN/docs/Web/API/Cache]
  4. MDN fetch [https://developer.mozilla.org/zh-CN/docs/Web/API/Fetch_API/Using_Fetch]
  5. Service Worker API [https://developer.mozilla.org/zh-CN/docs/Web/API/Service_Worker_API#service_worker_%E7%9A%84%E6%A6%82%E5%BF%B5%E5%92%8C%E7%94%A8%E6%B3%95]
  6. sw-test [https://github.com/mdn/sw-test]
  7. PWA 入门: 理解和创建 Service Worker 脚本 [https://zhuanlan.zhihu.com/p/25524382]
  8. SW 更新 https://harttle.land/2017/04/10/service-worker-update.html#header-0
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 213,864评论 6 494
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 91,175评论 3 387
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 159,401评论 0 349
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 57,170评论 1 286
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 66,276评论 6 385
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 50,364评论 1 292
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 39,401评论 3 412
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 38,179评论 0 269
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 44,604评论 1 306
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 36,902评论 2 328
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 39,070评论 1 341
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 34,751评论 4 337
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 40,380评论 3 319
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 31,077评论 0 21
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,312评论 1 267
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 46,924评论 2 365
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 43,957评论 2 351

推荐阅读更多精彩内容