Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

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

(I am paraphrasing question asked by Rich Harris in the "Stuff I wish I'd known sooner about service workers" gist.)

If I have code in my service worker that runs outside an event handler, when does it run?

And, closely related to that, what is the difference between putting inside an install handler and putting it outside an event handler entirely?

like image 229
Jeff Posnick Avatar asked Aug 08 '16 17:08

Jeff Posnick


People also ask

What is the event stage when a service worker is ready to serve the request?

The install event is the first event a service worker gets, and it only happens once. A promise passed to installEvent. waitUntil() signals the duration and success or failure of your install. A service worker won't receive events like fetch and push until it successfully finishes installing and becomes "active".

What can trigger execution of the event handler?

Event handler code can be made to run when an event is triggered by assigning it to the target element's corresponding onevent property, or by registering the handler as a listener for the element using the addEventListener() method.

How do I find out if a service worker is running?

You can look at Service Worker Detector, a Chrome extension that detects if a website registers a Service Worker by reading the navigator. serviceWorker.

What are the life cycle stages of a service worker?

The service worker lifecycle consists of mainly 3 phases, which are: Registration. Installation. Activation.


1 Answers

In general, code that's outside any event handler, in the "top-level" of the service worker's global scope, will run each and every time the service worker thread(/process) is started up. The service worker thread may start (and stop) at arbitrary times, and it's not tied to the lifetime of the web pages it controlled.

(Starting/stopping the service worker thread frequently is a performance/battery optimization, and ensures that, e.g., just because you browse to a page that has registered a service worker, you won't get an extra idle thread spinning in the background.)

The flip side of that is that every time the service worker thread is stopped, any existing global state is destroyed. So while you can make certain optimizations, like storing an open IndexedDB connection in global state in the hopes of sharing it across multiple events, you need to be prepared to re-initialize them if the thread had been killed in between event handler invocations.

Closely related to this question is a misconception I've seen about the install event handler. I have seen some developers use the install handler to initialize global state that they then rely on in other event handlers, like fetch. This is dangerous, and will likely lead to bugs in production. The install handler fires once per version of a service worker, and is normally best used for tasks that are tied to service worker versioning—like caching new or updated resources that are needed by that version. After the install handler has completed successfully, a given version of a service worker will be considered "installed", and the install handler won't be triggered again when the service worker starts up to handle, e.g., a fetch or message event.

So, if there is global state that needs to be initialized prior to handling, e.g., a fetch event, you can do that in the top-level service worker global scope (optionally waiting on a promise to resolve inside the fetch event handler to ensure that any asynchronous operations have completed). Do not rely on the install handler to set up global scope!

Here's an example that illustrates some of these points:

// 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`.     });   ); }); 
like image 130
Jeff Posnick Avatar answered Oct 07 '22 18:10

Jeff Posnick