手把手教你Service Worker

  Service Workers 是什么?它们能做什么,它如何让您的 web 应用更好的表现?本文旨在回答这些问题,以及如何使用 Ember.js 框架来实现 Service Worker。

  在 Web 早期,对于用户离线时网页应该怎样表现,基本没有方案。用户应该 总是 在线的。

  但是,由于移动互联网的到来,和世界上其它领域的发展,参差不齐的互联网连接在现代网络用户中已经越来越普遍了。

  因此,网站在离线时如何展现,将变得非常重要,以使用户不至于被网络可用性所限制。

  AppCache起初作为 HTML5 的特性被用作离线 web 应用的一种解决方案。 它包含以一个围绕缓存清单的 HTML 和 JS 的组合,一份以声明式语言编写的配置文件。

  AppCache 最终被认为笨拙且充满陷阱。它已经被弃用了,并被 Service Workers 所取代。

  Service workers提供了一个对离线问题更先进的解决方案。通过更严格、更程序化实现,替换了 AppCache 那种声明式的方式。

  Service Workers 是在 web 浏览器所包含的持久的后台进程中执行代码的一种方式。其中的代码是事件驱动的,意味着在 Service Worker 范围内触发的事件驱动着它的行为。

  文章剩余的部分将会对这些事件做一个简短的解释。但是要开始使用 Service Workers,还需要先在前置 web 应用里注册 Service Worker。

  下面的代码说明了怎样在客户端浏览器里注册Service Worker。通过在前置 web 应用中调用register方法即可完成注册:

  它同时也会设置您的 Service Worker 的范围。文件名/sw.js表示 Service Worker 的范围是 URL 的根路径(或),这意味着根路径下发出的请求都能过通过触发的事件被 Service Worker 捕捉,而/js/sw.js这样的文件名将只能捕获到下的请求。

  另外,您可以通过传递第二个参数给register方法,明确的设置 Service Worker 的范围:

  Install 事件非常有用,它可以让您在 Service Worker 初始化的时候执行逻辑。比如一个一次性的操作,为 Service Worker 的工作做好准备;普遍的例子是从安装步骤中载入缓存。

  caches是一个全局的CacheStorage对象,您可以通过它来管理浏览器缓存。我们通过调用open函数去获取具体的cache对象。

  cache.addAll将会请求URL 列表中的每一个文件,然后在各自的缓存中保存响应,它使用请求的 body 作为每一个缓存数据的 key。查阅addAll文档了解更多。

  fetch事件在每次网页发送请求的时候触发。当事件触发的时候,Service Worker 可以 拦截 请求并决定返回结果 - 可能是缓存数据,或者是真实的网络请求响应。

  下面的例子演示了一个 缓存优先 策略:任何与请求匹配的缓存数据会优先返回,不通过网络请求;只有在没有匹配的缓存数据的时候,才会发出网络请求。

  FetchEvent对象中的request变量包含请求的 body,它被用来查找响应匹配的缓存数据。

  cache.match将会尝试去查找请求对应的缓存数据。如果没有找到,promise 将会返回undefined。我们会首先检查是否有缓存数据,如果没有,就调用fetch方法发送网络请求,并返回 promise 。

  event.respondWith是 FetchEvent 特有的方法,我们用它返回一个响应给浏览器请求。它接受一个 Promise,用来返回响应数据(或网络错误)。

  Fetch 事件尤其重要,因为您的 缓存策略 都需要在此定义。比如:什么时候该使用缓存数据,什么时候又该使用网络数据。

  Service Workers 的魅力在于,它属于底层 API,可以拦截请求并让您决定怎样返回响应。这让我们可以自由的实施自己的策略,来获取缓存数据或网络内容。当您尝试为自己的 Web 应用实现最佳缓存时,您可以使用几种基本的缓存策略。

  Mozilla 有一个方便的资源记录了几种不同的缓存策略。另外,Jake Archibald 编写的一篇离线手册也介绍了同样的缓存策略,以及更多。

  在上面的例子中,我们演示了一个基本的缓存优先策略。下面是在我自己的项目工程中适用的一个例子:缓存更新策略。这个方法将优先响应缓存数据,随后在后台发送一个网络请求;后台请求的响应被用来更新缓存数据,因此,在接下来的请求中,更新后的缓存数据能够被访问到。

  event.respondWith用来提供请求响应。我们从缓存中获取匹配的数据,如果不存在,我们将会从网络上获取。

  接着我们调用event.waitUntil,允许一个异步的 Promise 在 Service Worker 上下文结束之前作出决策,然后缓存响应。一旦这个异步操作完成,waitUntil将返回并终止操作。

  Activate 事件少有文档说明。但是在你更新 Service Worker 文件并需要执行清理的时候,或在维护前一版本的 Service Worker 的时候,它将显得尤为重要。

  当网页关闭,然后重新打开,浏览器会将旧的 Service Worker 替换成新的,然后触发Activate事件,在Install事件之后。如果你需要清除缓存、或维护老版本的 Service Worker,Activate 事件是最好的时机。

  Sync 事件可以让网络任务延时,直到用户连通;该功能的实现通常被称为后台同步。对于确保在离线模式下,用户启动的任何与网络有关的任务,最终将在网络可用时重新工作,这是非常有帮助的。

  这里是一个关于后台同步的实现看起来的样子。您需要在前置 JS 中编写注册同步事件的代码,同时在 Service Worker 中处理同步事件:

  基本上,任何需要确保连接网络的操作,不管是即时操作还是网络离线后最终恢复的情况,都需要作为 sync 事件注册。

  例如提交评论、或者是获取用户数据之类的操作,都需要在 Service Worker 的事件处理中定义:

  我们在这监听同步事件,并检查SyncEvent对象上的tag,来确定是否是给点击的submit事件。

  对于这个例子来说,如果用户离线 次按钮,当网络恢复的时候,所有的同步注册都会被合并,且同步事件只会触发一次。

  在这个例子中,如果您想要分别同步每一次点击事件,需要给每个同步注册绑定唯一的标签。

  如果用户在线,则 sync 事件会立即触发,并毫无延迟的完成您所定义的任务。

  如果您跟我一样,想要在 Chrome 中试试,请确保网络已完全断开。在 Chome DevTools 中通过切换 Network 选择框,并不会触发 sync 事件。

  获取更多信息,您可以阅读这篇说明文档,以及这个后台同步介绍。sync 事件还未被广泛支持(在写这篇文章时,还只有 Chrome 支持),而且一定会经历很多变化,所以请保持关注。

  当讨论 Web 推送通知的时候,实际上涉及到俩种技术:通知 和 推送消息。

  首先,需要通过网页获得用户对通知的许可;之后就可以切换通知了,并能处理某些事件,例如:通知是否被用户关闭。

  推送消息涉及到调用由浏览器提供的 Push Api,再加上后端实现。Push Api 的实现需要通过单独一篇文章来讲解,但是基本概念如下图所示:

  这个过程稍微有点复杂,而且不在本文的讨论范围。但是如果您想了解更多,这篇推送通知介绍会有所帮助。

  抛开细节,可以看到这份代码基本实现了我们之前提到的三个事件的处理:install,activate和fetch。

  在fetch事件的处理中,我们检查了request是否满足一些条件(是否是GET请求、是否请求的 HTML 内容;是否来源于当前路径等);如果满足这些条件,就返回缓存中的内容。

  注意,我们使用cache.match方法和INDEX_HTML_URL查找数据,而不是request.url,表示我们只通过 key 查找缓存,而不用关心当前的路径。

  如果发现已有的 ember-service-worker 插件不能很好的满足需求,您可以创建自己的插件,具体请查看ember-service-worker 网站上的官方文档。

  希望您已经透彻的理解了 Service Workers 和它的底层架构,以及 web 应用该怎样利用它给用户代来更好的体验。

相关阅读