Search code examples
javascriptweb-workerservice-worker

How can I access Web Workers in a Service Worker?


I'm working with serviceworker quite a bit lately and often run into a situation where I would for like to process some raw data fetched before storing it into the serviceworker cache - example use case would be to process a large raw text file to remove unnecessary white space. This way the cached response to my http request would already be "optimized".

I was thinking that why not do this in webworker, but alas, after much searching I have not found any idea of how a webworker could be made accessible inside the serviceworker. It's not like I can pass in the webworker context using postMessage.

Question:
How can I access Web Workers in a Service Worker?


Solution

  • It's currently not possible to access a web worker from within a service worker. This might change in the future, and the relevant standards issue is https://github.com/whatwg/html/issues/411

    Note that it's possible to use the Cache Storage API from within a web worker that's spawned by a normal web page, so you could theoretically do what you suggest outside the context of a service worker.

    This is a matter of personal preference rather than a strict guideline, but I don't like the pattern of modifying the data you get back from the network and then using the Cache Storage API to persist it in a synthetic Response object. I prefer using the Cache Storage API for keeping exact copies of what you get back from the network, so that things look the same to your controlled page regardless of whether the request is fulfilled from the network or from the cache.

    A pattern that I've used before, and has the added benefit of using of web workers in the way you suggest, is to use IndexedDB in a similar manner. If the response is already in IndexedDB, then you just use it, and if it's not, then you kick off a web worker to handle the network request and processing, and then store the result in IndexedDB for future use.

    Here's an example of some code to do this, making use of a lot of ES2015+ features, along with the promise-worker and idb-keyval libraries for the asynchronous code.

    import PromiseWorker from 'promise-worker';
    import idbKeyValue from 'idb-keyval';
    
    export default async (url, Worker) => {
      let value = await idbKeyValue.get(url);
      if (!value) {
        const promiseWorker = new PromiseWorker(new Worker());
        value = await promiseWorker.postMessage(url);
        // Don't await here, so that we can return right away.
        idbKeyValue.set(url, value);
      }
      return value;
    };
    

    And then the worker could look something like this (which converts Markdown to HTML):

    import 'whatwg-fetch';
    import MarkdownIt from 'markdown-it';
    import registerPromiseWorker from 'promise-worker/register';
    
    const markdown = new MarkdownIt();
    
    registerPromiseWorker(async url => {
      const response = await fetch(url);
      const text = await response.text();
      return markdown.render(text);
    });
    

    This approach would start making less sense if you're dealing with large amounts of data, because there's an overhead in serialization, and lack of streaming support, compared to what would be possible with just using the Cache Storage API directly.