什么时候事件处理程序之外的服务工作者中的代码 运行?

When does code in a service worker outside of an event handler run?

(我正在解释 Rich Harris 在“Stuff I wish I'd known sooner about service workers”要点中提出的问题。)

如果我的 service worker 中有代码 运行 在事件处理程序之外,它什么时候 运行?

而且,与此密切相关的是,将 install 处理程序放入内部和将其完全放在事件处理程序外部之间有什么区别?

一般来说,任何事件处理程序之外的代码,在服务工作者全局范围的 "top-level" 中,将 运行 每次启动服务工作者线程(/进程) . Service Worker 线程可以在任意时间启动(和停止),并且与它控制的网页的生命周期无关。

(Starting/stopping service worker 线程经常是 performance/battery 优化,并确保,例如,仅仅因为你浏览到一个已经注册了 service worker 的页面,你就不会得到一个额外的空闲线程在后台旋转。)

不利的一面是,每次服务工作者线程停止时,任何现有的全局状态都会被销毁。因此,虽然您可以进行某些优化,例如在全局状态中存储一个打开的 IndexedDB 连接以希望在多个事件之间共享它,但如果线程在事件处理程序调用之间被杀死,您需要准备好重新初始化它们。

与这个问题密切相关的是我对 install 事件处理程序的误解。我看到一些开发人员使用 install 处理程序来初始化全局状态,然后他们在其他事件处理程序中依赖这些状态,例如 fetch。这很危险,并且可能会导致生产中出现错误。 install 处理程序在每个服务工作者版本中触发一次,通常最适合用于与服务工作者版本控制相关的任务——比如缓存该版本所需的新资源或更新资源。在 install 处理程序成功完成后,给定版本的 service worker 将被视为 "installed",并且当 service worker 启动处理时不会再次触发 install 处理程序,例如 fetchmessage 事件。

因此,如果在处理之前需要初始化全局状态,例如 fetch 事件,您可以在顶级服务工作者全局范围内执行此操作(可选择等待承诺在 fetch 事件处理程序内部解析以确保所有异步操作都已完成)。 不要依赖install处理程序来设置全局范围!

这里有一个例子可以说明其中的一些要点:

// Assume this code lives in service-worker.js

// This is top-level code, outside of an event handler.
// You can use it to manage global state.

// _db will cache an open IndexedDB connection.
let _db;
const dbPromise = () => {
  if (_db) {
    return Promise.resolve(_db);
  }

  // Assume we're using some Promise-friendly IndexedDB wrapper.
  // E.g., https://www.npmjs.com/package/idb
  return idb.open('my-db', 1, upgradeDB => {
    return upgradeDB.createObjectStore('key-val');
  }).then(db => {
    _db = db;
    return db;
  });
};

self.addEventListener('install', event => {
  // `install` is fired once per version of service-worker.js.
  // Do **not** use it to manage global state!
  // You can use it to, e.g., cache resources using the Cache Storage API.
});

self.addEventListener('fetch', event => {
  event.respondWith(
    // Wait on dbPromise to resolve. If _db is already set, because the
    // service worker hasn't been killed in between event handlers, the promise
    // will resolve right away and the open connection will be reused.
    // Otherwise, if the global state was reset, then a new IndexedDB
    // connection will be opened.
    dbPromise().then(db => {
      // Do something with IndexedDB, and eventually return a `Response`.
    });
  );
});