diff --git a/.changeset/fluffy-pears-press.md b/.changeset/fluffy-pears-press.md new file mode 100644 index 0000000000..03eadc1347 --- /dev/null +++ b/.changeset/fluffy-pears-press.md @@ -0,0 +1,5 @@ +--- +"astro": patch +--- + +Fix streaming in Node.js fast path diff --git a/packages/astro/src/runtime/server/render/astro/render.ts b/packages/astro/src/runtime/server/render/astro/render.ts index f918f55c11..e945551d86 100644 --- a/packages/astro/src/runtime/server/render/astro/render.ts +++ b/packages/astro/src/runtime/server/render/astro/render.ts @@ -209,14 +209,23 @@ export async function renderToAsyncIterable( let error: Error | null = null; // The `next` is an object `{ promise, resolve, reject }` that we use to wait // for chunks to be pushed into the buffer. - let next = promiseWithResolvers(); + let next: ReturnType> | null = null; const buffer: Uint8Array[] = []; // []Uint8Array + let renderingComplete = false; const iterator: AsyncIterator = { async next() { if (result.cancelled) return { done: true, value: undefined }; - await next.promise; + if(next !== null) { + await next.promise; + } + + // Only create a new promise if rendering is still ongoing. Otherwise + // there will be a dangling promises that breaks tests (probably not an actual app) + if(!renderingComplete) { + next = promiseWithResolvers(); + } // If an error occurs during rendering, throw the error as we cannot proceed. if (error) { @@ -276,8 +285,7 @@ export async function renderToAsyncIterable( // Push the chunks into the buffer and resolve the promise so that next() // will run. buffer.push(bytes); - next.resolve(); - next = promiseWithResolvers(); + next?.resolve(); } }, }; @@ -286,12 +294,14 @@ export async function renderToAsyncIterable( renderPromise .then(() => { // Once rendering is complete, calling resolve() allows the iterator to finish running. - next.resolve(); + renderingComplete = true; + next?.resolve(); }) .catch((err) => { // If an error occurs, save it in the scope so that we throw it when next() is called. error = err; - next.resolve(); + renderingComplete = true; + next?.resolve(); }); // This is the Iterator protocol, an object with a `Symbol.asyncIterator` diff --git a/packages/astro/test/streaming.test.js b/packages/astro/test/streaming.test.js index 93172aa72a..cbd4fa4f4b 100644 --- a/packages/astro/test/streaming.test.js +++ b/packages/astro/test/streaming.test.js @@ -37,7 +37,7 @@ describe('Streaming', () => { let chunk = decoder.decode(bytes); chunks.push(chunk); } - assert.equal(chunks.length > 1, true); + assert.equal(chunks.length > 5, true); }); it('Body of slots is chunked', async () => {