[译] Service workers:PWA背后的英雄

原文地址:medium.freecodecamp.org/service-wor…
做者:Flavio Copes
摘要:这篇文章简述service worker做为PWA核心技术如何实现资源缓存和消息推送的功能,还帮助读者理解service worker的生命周期。git

Service worker是渐进式网络应用(Progressive Web Apps)的核心。它们帮助咱们实现本来是原生app才有资源缓存和消息推送两大特性。github

Service worker是你的网页与网络间的代理,它可以拦截和缓存来往的网络请求。这能够帮助你的应用创造一个离线环境下也能良好访问的用户体验。web

首先介绍一下web worker的概念。它是一个与指定网页相关联的JS文件,独立与主线程运行在一个特定的上下文环境中,这样就不会为了计算数据去牺牲UI的性能,从而避免了阻塞的状况。而service worker是一种特殊的web worker。chrome

而正是因为它是一个子线程,因此没法操做DOM。一样也没法访问Local Storage API和XHR API。它只能经过Channel Messaging API和主线程通讯。shell

Service Worker可以与下面几个API合做:后端

  • Promises
  • Fetch API
  • Cache API

只有在HTTPS协议下的网页里它们才会起做用。(不过不包括本地的网络请求,由于它们不须要保持安全链接。这样也方便咱们调试。)promise

后台进程

Service worker可以独立于与它关联的应用程序运行,而且在这些程序处于非活跃状态下仍能够接收消息。浏览器

让我来举几个场景:缓存

  • app处于后台非活跃状态下运行;
  • app被关闭;
  • 呈现你网页的浏览器被关闭;

那么service worker将不受影响地继续工做。安全

Service worker的有用之处在于:

  • 它们能够看成缓冲层,处理网络请求和缓存离线所需的资源;
  • 它们能够用来推送消息。

Service worker只在须要的时候运行,其余状况下都会中止工做。

支持离线

对于传统网页,离线状况下的用户体验很是糟糕。若是用户没有联网,移动端的web应用通常是直接中止工做。反观原生应用,会展现给用户一些友好的提示信息。

下面这张图是Chrome浏览器中离线网页显示的内容,显然这并不算是一个友好的提示信息:

chrome离线

也许惟一不错的地方是你能够经过点击恐龙来免费玩一个游戏,不过相信你很快就会变得不耐烦了。

无聊的游戏

不久以前,HTML5标准下的AppCache可让web应用缓存离线资源,可是它缺少灵活性而且有一些使人困惑的行为,这也说明它没法胜任支持离线这项工做。

而如今,service worker成了离线缓存的新标准。

那么,它实现哪些缓存呢?

安装时的预缓存

像图片、CSS文件和JS文件都会在app的使用过程当中重复用到。这些资源能够在app打开的第一时间缓存好。

这也是所谓的APP壳架构( App Shell architecture)的基础。

缓存网络请求

咱们使用Fetch API能够对服务器返回的响应报文进行编辑,根据服务器是否可达来决定是否使用缓存中的响应报文代替。

生命周期

一个service worker在启动前经历了三步:

  • 注册(Registration)
  • 安装(Installation)
  • 激活(Activation)

注册

注册阶段是通知浏览器service worker的存在,而且在后台开始安装。

下面是写在worker.js中注册一个service worker的代码:

if ('serviceWorker' in navigator) { 
  window.addEventListener('load', () => {   
    navigator.serviceWorker.register('/worker.js') 
    .then((registration) => { 
      console.log('Service Worker registration completed with scope: ', registration.scope) 
    }, (err) => { 
      console.log('Service Worker registration failed', err)
    })
  })
} else { 
  console.log('Service Workers not supported') 
}
复制代码

不管这段代码被调用多少次,浏览器始终只会在service worker以前没有注册过或是须要更新的状况下进入注册阶段。

Scope

register函数须要一个scope参数来指明你的web应用被该service worker管理的文件所在路径。

这个参数的默认值是全部文件以及service worker文件父级目录下的全部子文件夹。因此若是你把service worker文件放在根目录下,它会管理整个web应用。而若是在某个子文件夹中,它只会管理该路径可以访问的网页。

下面这个例子经过指定scope参数为/notifications/目录来注册一个service worker。

navigator.serviceWorker.register('/worker.js', { 
  scope: '/notifications/' 
})
复制代码

结尾的/很是重要,能够避免/notification页面触发service worker。而若是写成下面这样:

{ scope: '/notifications' }
复制代码

那么service worker就将一样做用于/notification页面。

注意:service worker没法控制自身所在目录之外的文件。也就是说,若是service worker文件被放在/notification文件夹下,它没法控制根目录/或其余不属于/notification的文件。

安装

若是浏览器发现一个service worker过时或以前没有注册过,那么它将安装这个service worker。

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

这是使用service worker初始化缓存,而后利用Cache API来缓存APP shell和静态资源的好时机。

激活

一旦service worker注册并安装成功后 ,咱们来到了第三阶段:激活。

这时,service worker可以在加载新页面时开始工做。

它不能做用于激活前已经加载过的页面,因此只有重启app或是刷新已加载页面两种方式来使它工做。

self.addEventListener('activate', (event) => { 
  //... 
});
复制代码

监听这个事件能够用来清除旧缓存或者是删除新版service worker不须要的旧资源。

更新

你仅仅是修改一字节的文件就须要更新一次service worker。它会在注册的代码再次执行时被更新。

更新后的service worker只有在全部页面都被关闭后才开始代替以前的service worker工做。若是仅仅是刷新页面是不会起做用的,由于以前的service worker仍然在运行且没有被删除。

这种机制保证了更新不会让以前在运行的app或网页崩溃。

Fetch事件

当浏览器发送网络请求时就会触发Fetch事件。

咱们借此能够在请求发送时检查缓存中是否已经存储所需资源。

举个例子,下面的代码使用了Cache API来检查请求的URL是否已经被缓存。若是是,那么返回缓存中的响应数据,不然会发送请求而后返回响应数据。

self.addEventListener('fetch', (event) => {
  event.respondWith( 
    caches.match(event.request) 
      .then((response) => { 
        if (response) { 
          //entry found in cache 
          return response 
        } 
        return fetch(event.request) 
      } 
    ) 
  ) 
})
复制代码

Background Sync

当用户在离线状态下发送网络请求时,Background Sync这个API将延迟该请求直到用户脱离离线状态。

这保证了用户在离线状态下仍然可使用并操做app,这些离线操做会保存在队列中,以便在链接网络后向服务端发出响应请求。(是否是比展现一个无休止的loading图标要好多了?)

navigator.serviceWorker.ready.then((swRegistration) => { 
    //注册一个事件event1
  return swRegistration.sync.register('event1') 
});
复制代码

下面的代码是在service worker中监听这个事件:

self.addEventListener('sync', (event) => { 
  if (event.tag == 'event1') { 
    event.waitUntil(doSomething()) 
  } 
})
复制代码

doSomething()返回一个promise。若是返回的promise被拒,另外一个同步事件被自动地开始重试操做,直到返回一个成功状态的promise。

这也使得app可以在联网时马上更新服务器发来的数据。

消息推送

Service worker使得web应用能够像原生同样推送消息给用户。

事实上,推送和消息通知是两个不一样的概念,它们组合而成的技术才是咱们熟悉的消息推送。推送机制使得服务器可以向service worker发送信息,而后service worker将信息展现给用户才是消息通知。

因为service worker能够在app关闭后继续运行,因此它们可以一直监听推送事件。而后它们能够发送消息通知,或者是更新app的状态。

推送事件会在后端经过浏览器推送服务后启动,好比说Firebase的推送服务。

下面的代码演示了web worker如何监听push事件:

self.addEventListener('push', (event) => { 
  console.log('Received a push event', event) 
  const options = { 
    title: 'I got a message for you!', 
    body: 'Here is the body of the message', 
    icon: '/img/icon-192x192.png', 
    tag: 'tag-for-this-notification', 
  } 
  event.waitUntil( 
    self.registration.showNotification(title, options) 
  ) 
})
复制代码

关于console.log

若是你的代码中包含console.log或是其余相似的控制台输出语句,务必打开Chrome开发工具中的Preserve log功能。

不然的话,由于service worker在网页加载前就开始执行,而此时控制台会被清空,因此你没法看到任何日志输出。

做者总结

很是感谢阅读本篇教程,事实上,关于PWA还有不少要学习的知识。若是您有什么看法欢迎在下面评论。

译者注:主流浏览器开始逐渐支持service worker,之后PWA是否会真的与原平生分秋色呢?将来如何,我想如今多了解一点PWA的知识总不会坏事。
项目地址:github.com/WhiteYin/tr…

相关文章
相关标签/搜索