Worker streaming issue: This writable stream writer has been released

Hi, I’m trying to build a worker that wraps an origin’s response in a JSONP callback by adding a prefix and suffix, e.g:

myCallback({
  /* origin response here */
});

My code is fairly close to the preamble example in the WritableStreamDefaultWriter docs, with the difference that I’m passing preventClose: true when using pipeTo() with the original body, since I need the writeable stream to stay open to write the ending parenthesis afterwards.

The completed response seems to be coming through fine, but the Workers playground keeps showing an error message of TypeError: This writable stream writer has been released, even though I can’t see any cases of my code using a stream writer after releasing it.

There’s a minimal example here:

https://cloudflareworkers.com/#fbe6e46756ce702a55a891d311ba26cf:https://example.com/

And the full code is:

addEventListener('fetch', event => {
  event.respondWith(handleRequest(event.request))
})

function addJsonpCallback(response, callbackName) {    
    const { readable, writable } = new TransformStream();
    const encoder = new TextEncoder();

    const wrap = async () => {
        const prefixWriter = writable.getWriter();
        await prefixWriter.write(encoder.encode(`${callbackName}(`));
        prefixWriter.releaseLock();
        console.log('Prefix done');

        await response.body.pipeTo(writable, { preventClose: true });
        console.log('Body done');

        const suffixWriter = writable.getWriter();
        await suffixWriter.write(encoder.encode(`); // END`));
        await suffixWriter.close();
        console.log('Suffix done');
    };

    wrap();

    return new Response(readable, response);
};

async function handleRequest(request) {
  const jsonReq = new Request('https://jsonplaceholder.typicode.com/photos')
  const response = await fetch(jsonReq);
  return addJsonpCallback(response, 'exampleCallback')
}

The console output I see is:

Prefix done
Uncaught (in promise) TypeError: This writable stream writer has been released.
    at wrap (VM3 worker.js:12)
Body done
Suffix done

I wondered if anyone knows what the issue is here? I’m still pretty new to the streams API. Thanks!

I managed to simplify the code further, and I’m actually getting the same error with what is essentially the verbatim example from WritableStreamDefaultWriter#releaseLock():

https://cloudflareworkers.com/#f02b267ea8d3a32f4e0b0161bd540a51:https://example.com/

Sample code from the docs:

let writer = writable.getWriter()
// Write a preamble.
writer.write(new TextEncoder().encode("foo bar"))
// While that’s still writing, pipe the rest of the body from somewhere else.
writer.releaseLock()
await someResponse.body.pipeTo(writable)

The failing code:

function addPreamble(originResponse) {
    const { readable, writable } = new TransformStream();

    const wrap = async () => {
        const writer = writable.getWriter();
        await writer.write((new TextEncoder()).encode(`/* Hello */`));
        writer.releaseLock();
        console.log('Prefix done');

        await originResponse.body.pipeTo(writable);
        console.log('Body done');
    };

    wrap();

    return new Response(readable, originResponse);
};

async function handleRequest(request) {
  const response = await fetch('https://jsonplaceholder.typicode.com/photos');
  return addPreamble(response);
}

addEventListener('fetch', event => {
  event.respondWith(handleRequest(event.request))
})