Skip to content

Instantly share code, notes, and snippets.

Embed
What would you like to do?
Service worker stream transferring
"use strict";
const worker = new Worker("worker.js");
self.onfetch = e => {
const transform = new TransformStream(); // creates an identity transform
e.respondWith(new Response(transform.readable));
// Give the worker the writable end. An identity transform stream will just shuffle
// bytes written there into transform.readable.
worker.postMessage(transform.writable, [transform.writable]);
// transform.writable has now been transferred/neutered/detached.
// This means that transform.writable.getWriter() will never work anymore in this thread.
// However, the *creator* of transform.writable (i.e., the logic inside
// new TransformStream()) can still see data that is written to it. That logic
// will then shuffle it over to transform.readable.
};
"use strict";
self.onmessage = ({ data: writableStream }) => {
const writer = writableStream.getWriter();
writer.write(new Uint8Array([1, 2, 3, 4]));
writer.close();
// You can, of course, do the writing/closing asynchronously.
};
@kettanaito
Copy link

kettanaito commented Oct 17, 2022

Hey, @domenic. Thank you for this example.

Do you have any idea why responding with a ReadableStream from a service worker would render an unreadable response on the client?

// worker.js
self.addEventListener('fetch', (event) => {
  const stream = new ReadableStream({
    start(controller) {
      controller.enqueue('hello')
      controller.close()
    }
  })
  const response = new Response(stream)
  event.respondWith(response)
})

Whenever I try to read this response on the client, I get a FetchError:

fetch('/resource').then(r => r.text())
// TypeError: Failed to Fetch

I apologize if this is a question indirectly related to the gist above but I could really use your advice. Thanks.

Answer

Use new TextEncoder().encode('data') before passing it to the stream.

@guest271314
Copy link

guest271314 commented Oct 17, 2022

Are you performing any other fetches client side? fetch event can be called multiple times for the document itself, icons, etc. You can include a query string in the URL requested , or use other means to identify the request where a Response() with ReadableStream passed is expected

  if (event.request.url.includes('stream')) {
    event.respondWith(
      new Response(readable, {
        cache: 'no-store',
        headers: { 'Content-Type': 'application/octet-stream' },
      })
    );
    console.log(readable);
  }
`

@kettanaito
Copy link

kettanaito commented Oct 17, 2022

@guest271314, no, I'm not. It's a test page and I'm doing only that request. For some reason, the way I'm handling it in the example above is incorrect. So it's not a request-matching issue. I do have a slightly more complex setup than above, so maybe some other things affect this.

@guest271314
Copy link

guest271314 commented Oct 18, 2022

@kettanaito Try passing a Uint8Array to enqueue().

@kettanaito
Copy link

kettanaito commented Oct 20, 2022

@guest271314, yes, that was the root cause! 🎉 I've come to discover it as well. You should transfer encoded data over ReadableStream.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment