From d7d77c6e1a662eeda66a69fa711c938bff04360d Mon Sep 17 00:00:00 2001 From: Raz Luvaton <16746759+rluvaton@users.noreply.github.com> Date: Mon, 18 Sep 2023 17:17:39 +0300 Subject: [PATCH] stream: improve webstream readable async iterator performance PR-URL: https://github.com/nodejs/node/pull/49662 Reviewed-By: Matteo Collina Reviewed-By: Luigi Pinca Reviewed-By: Yagiz Nizipli Reviewed-By: James M Snell Reviewed-By: Benjamin Gruenbaum Reviewed-By: Moshe Atlow --- lib/internal/webstreams/readablestream.js | 73 ++++++++++++++--------- 1 file changed, 44 insertions(+), 29 deletions(-) diff --git a/lib/internal/webstreams/readablestream.js b/lib/internal/webstreams/readablestream.js index 28e985875c5ddb..a2727d94b6da4b 100644 --- a/lib/internal/webstreams/readablestream.js +++ b/lib/internal/webstreams/readablestream.js @@ -477,9 +477,13 @@ class ReadableStream { // eslint-disable-next-line no-use-before-define const reader = new ReadableStreamDefaultReader(this); - let done = false; + + // No __proto__ here to avoid the performance hit. + const state = { + done: false, + current: undefined, + }; let started = false; - let current; // The nextSteps function is not an async function in order // to make it more efficient. Because nextSteps explicitly @@ -488,7 +492,7 @@ class ReadableStream { // unnecessary Promise allocations to occur, which just add // cost. function nextSteps() { - if (done) + if (state.done) return PromiseResolve({ done: true, value: undefined }); if (reader[kState].stream === undefined) { @@ -498,31 +502,15 @@ class ReadableStream { } const promise = createDeferredPromise(); - readableStreamDefaultReaderRead(reader, { - [kChunk](chunk) { - current = undefined; - promise.resolve({ value: chunk, done: false }); - }, - [kClose]() { - current = undefined; - done = true; - readableStreamReaderGenericRelease(reader); - promise.resolve({ done: true, value: undefined }); - }, - [kError](error) { - current = undefined; - done = true; - readableStreamReaderGenericRelease(reader); - promise.reject(error); - }, - }); + // eslint-disable-next-line no-use-before-define + readableStreamDefaultReaderRead(reader, new ReadableStreamAsyncIteratorReadRequest(reader, state, promise)); return promise.promise; } async function returnSteps(value) { - if (done) + if (state.done) return { done: true, value }; // eslint-disable-line node-core/avoid-prototype-pollution - done = true; + state.done = true; if (reader[kState].stream === undefined) { throw new ERR_INVALID_STATE.TypeError( @@ -559,19 +547,19 @@ class ReadableStream { // need to investigate if it's a bug in our impl or // the spec. if (!started) { - current = PromiseResolve(); + state.current = PromiseResolve(); started = true; } - current = current !== undefined ? - PromisePrototypeThen(current, nextSteps, nextSteps) : + state.current = state.current !== undefined ? + PromisePrototypeThen(state.current, nextSteps, nextSteps) : nextSteps(); - return current; + return state.current; }, return(error) { - return current ? + return state.current ? PromisePrototypeThen( - current, + state.current, () => returnSteps(error), () => returnSteps(error)) : returnSteps(error); @@ -773,6 +761,33 @@ function createReadableStreamBYOBRequest(controller, view) { return stream; } +class ReadableStreamAsyncIteratorReadRequest { + constructor(reader, state, promise) { + this.reader = reader; + this.state = state; + this.promise = promise; + } + + [kChunk](chunk) { + this.state.current = undefined; + this.promise.resolve({ value: chunk, done: false }); + } + + [kClose]() { + this.state.current = undefined; + this.state.done = true; + readableStreamReaderGenericRelease(this.reader); + this.promise.resolve({ done: true, value: undefined }); + } + + [kError](error) { + this.state.current = undefined; + this.state.done = true; + readableStreamReaderGenericRelease(this.reader); + this.promise.reject(error); + } +} + class DefaultReadRequest { constructor() { this[kState] = createDeferredPromise();