Search code examples
javascriptservice-worker

Use Asynchronous ReadableStream with Response to return HTML from fetch event of Service Worker


This question is similar to my other question but takes into account asynchronous nature inside the ReadableStream.

This is my test code now:

const stream = new ReadableStream({
    start(c) {
        let i = 1
        const t = setInterval(test, 5e2)
        function test() {
            c.enqueue("<h1>Yellow!</h1>")
            if (i++ === 5) {
                clearInterval(t)
                c.close()
            }
        }
    }
})
event.respondWith(new Response(stream, {headers: {"content-type": "text/html"}}))

The above code is not rendered by the browser and I'm not sure why. According to examples online it seems like it should work. But it doesn't appear to be reading any of the enqueue items.

Note in my other question the answer at least rendered the first response:

const createStream = () => new ReadableStream({
  start(controller) {
    controller.enqueue("<h1 style=\"background: yellow;\">Yellow!</h1>")
    controller.close()
  }
})

const secondStream = createStream().getReader();
secondStream.read()
  .then(({
    value
  }) => {

    return new Response(value, {
      headers: {
        "Content-Type": "text/html"
      }
    });

  })
  .then(response => response.text())
  .then(text => {
    console.log(text);
  });

Which seems to make sense as it is reading everything at once without regard to if the stream is finished.

Resources that I have used:

https://developer.mozilla.org/en-US/docs/Web/API/ReadableStream#Examples

https://philipwalton.com/articles/smaller-html-payloads-with-service-workers/


Solution

  • Looks like stream likes when you are putting Uint8Array in controller.enqueue method.

    With the help of TextEncoder your example works.

    const data = [
      {name: "Yellow", value: 'yellow'},
      {name: "Green", value: 'green'},
      {name: "Blue", value: 'blue'},
      {name: "Red", value: 'red'},
      {name: "Orange", value: 'orange'},
    ]
    
    const encoder = new TextEncoder();
    
    const stream = new ReadableStream({
        start(controller) {
            let i = 1
            const handle = setInterval(test, 5e2)
            function test() {
                const {name, value} = data[i - 1];
                controller.enqueue(encoder.encode(`<h1 style="color: ${value};">${name}!</h1>`))
                if (i++ === 5) {
                    clearInterval(handle)
                    controller.close()
                }
            }
        }
    })
    
    new Response(stream, {headers: {"content-type": "text/html"}})
      .text().then(text => {
        document.body.innerHTML = text;
      })