I just released a new version of my password manager which replaces the old and deprecated application cache (for offline operation) with the new ServiceWorker API.

In the process, I learned a few important things about ServiceWorker development which are not really explained clearly by various tutorials or documentation:

1. The Firefox Development Tools don't Support WebWorkers/ServiceWorker Development

At first, I was trying to get started using Firefox, but it was a complete failure. I could not see error messages, hit breakpoints, or print logs. However, as soon as I started testing in Chromium, my breakpoints would hit and I would see error messages for errors thrown inside the worker.

2. The ServiceWorker runs in its own little world where console.log doesn't exist.

Folks who have worked on other WebWorkers before may be familiar with this, but it certainly took me for a ride. I could not tell what was going on in my ServiceWorker because I could not see any output of any kind at first. In my case, this is what I did in order to get logs to print:

  1. Obtain a reference to the "client", that is, the web page that is utilizing this service worker.
  2. Send a message to that client.
  3. The client (web page) has an event handler for that message which prints the log message using console.log.

in serviceworker.js:

self.addEventListener('fetch', event => {


    // start a promise chain by attempting to grab the client
    clients.get(event.clientId).then((client) => {

      // now that we have the client, we continue chaining by
      // returning a promise which resolves to the actual handler response.
      return caches.match(event.request).then(response => {

        // Finally, now that we have the client and the handler logic is happening, we can log messages:
        if(client) {
          client.postMessage({ log: `request: ${event.request.method}, ${event.request.url}` });

in application.js, which is loaded by the main page:

    navigator.serviceWorker.addEventListener('message', event => {
      if(event.data.log) {

NOTE: When you are adding logging to a serviceworker event handler, It's really important that the main logic of the handler is not delayed by the asynchronous process of obtaining the client id, otherwise the event handler will "finish" itself before your logic starts, throwing an error.

Here is an example of that. When I first started out, My handler looked like this:

self.addEventListener('fetch', event => {
  event.respondWith(caches.match(event.request).then(response => {

I modified it to attempt to add logging support:

self.addEventListener('fetch', async event => {
  const client = await clients.get(event.clientId);
  if(client) {
    client.postMessage({ log: `test` });
  event.respondWith(caches.match(event.request).then(response => {

This was where I ran into the error message: Failed to execute 'respondWith' on 'FetchEvent': The event handler is already finished.