Search code examples
javascripthtmlweb-workerpreload

Preloading a “fetch” resource for use in a web worker


tl;dr Resources pre-loaded as

<link rel="preload" href="..." as="fetch" crossorigin="anonymous">

don’t seem to be used by a fetch call that occurs in a Web Worker.

Why is this? Is there a change I need to make to the preload tag, or another workaround?


Context: I have a Web Worker which downloads and processes a large amount of CSV data. The processing is quite intensive, so I’d like to keep it off the main thread.

However, I’d love a way to initiate the download earlier, so that the worker can get start processing it sooner after the worker starts.

I tried adding add a <link rel="preload" as="fetch" ...> for the resource, and that works great for a fetch from the main thread—but if I fetch the same resource from the worker, it the preloaded resource is not used (a separate request is initiated and I can see the standard “resource was preloaded using link preload but not used within a few seconds from the window's load event” warning printed to the console).


Solution

  • Indeed it seems that even though Workers do have the same (a clone) Policy Container as their owner Document, that's about it, and the map of preloaded resources isn't shared.

    So for your case what you could do is to preload in the main thread using your <link>, fetch() from there too, consume the Response as an ArrayBuffer (it's the rawest format) and transfer that ArrayBuffer to your Worker:

    <link rel="preload" href="..." as="fetch" crossorigin="anonymous">
    
      // ...
      const resp = await fetch("...");
      if (!resp.ok) { handleNetworkError(resp) }
      const buffer  = await resp.arrayBuffer();
      const worker = new Worker(/*...*/);
      worker.postMessage({ buffer }, [buffer]);
    

    And in your Worker you'd do

      onmessage = ({ data }) => {
        if (data.buffer) {
          const csvText = new TextDecoder().decode(data.buffer);
          parseCSV(csvText);
        }
      };
    

    Doing this way, you still take advantage of the preloading, and you still have a very minimal impact on the main thread: the fetching is done in parallel, consuming the Response as ArrayBuffer doesn't cost any computation, and neither should transferring the data to the Worker. But it's a bit more convoluted than just fetching from the Worker directly.

    Another solution depending on your case might be to use a ServiceWorker to pre-fetch your resources and cache them. Your Worker would then be able to tap in the cache.