stream: improve from perf

PR-URL: https://github.com/nodejs/node/pull/50359
Reviewed-By: Vinícius Lourenço Claro Cardoso <contact@viniciusl.com.br>
Reviewed-By: Robert Nagy <ronagy@icloud.com>
Reviewed-By: Benjamin Gruenbaum <benjamingr@gmail.com>
This commit is contained in:
Raz Luvaton 2023-10-26 11:42:58 +03:00 committed by GitHub
parent 4ddb263654
commit 10d51e82a6
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23

View File

@ -36,6 +36,7 @@ function from(Readable, iterable, opts) {
throw new ERR_INVALID_ARG_TYPE('iterable', ['Iterable'], iterable); throw new ERR_INVALID_ARG_TYPE('iterable', ['Iterable'], iterable);
} }
const readable = new Readable({ const readable = new Readable({
objectMode: true, objectMode: true,
highWaterMark: 1, highWaterMark: 1,
@ -46,11 +47,19 @@ function from(Readable, iterable, opts) {
// Flag to protect against _read // Flag to protect against _read
// being called before last iteration completion. // being called before last iteration completion.
let reading = false; let reading = false;
let isAsyncValues = false;
readable._read = function() { readable._read = function() {
if (!reading) { if (!reading) {
reading = true; reading = true;
next();
if (isAsync) {
nextAsync();
} else if (isAsyncValues) {
nextSyncWithAsyncValues();
} else {
nextSyncWithSyncValues();
}
} }
}; };
@ -78,29 +87,115 @@ function from(Readable, iterable, opts) {
} }
} }
async function next() { // There are a lot of duplication here, it's done on purpose for performance
// reasons - avoid await when not needed.
function nextSyncWithSyncValues() {
for (;;) { for (;;) {
try { try {
const { value, done } = isAsync ? const { value, done } = iterator.next();
await iterator.next() :
iterator.next();
if (done) { if (done) {
readable.push(null); readable.push(null);
} else { return;
const res = (value &&
typeof value.then === 'function') ?
await value :
value;
if (res === null) {
reading = false;
throw new ERR_STREAM_NULL_VALUES();
} else if (readable.push(res)) {
continue;
} else {
reading = false;
}
} }
if (value &&
typeof value.then === 'function') {
return changeToAsyncValues(value);
}
if (value === null) {
reading = false;
throw new ERR_STREAM_NULL_VALUES();
}
if (readable.push(value)) {
continue;
}
reading = false;
} catch (err) {
readable.destroy(err);
}
break;
}
}
async function changeToAsyncValues(value) {
isAsyncValues = true;
try {
const res = await value;
if (res === null) {
reading = false;
throw new ERR_STREAM_NULL_VALUES();
}
if (readable.push(res)) {
nextSyncWithAsyncValues();
return;
}
reading = false;
} catch (err) {
readable.destroy(err);
}
}
async function nextSyncWithAsyncValues() {
for (;;) {
try {
const { value, done } = iterator.next();
if (done) {
readable.push(null);
return;
}
const res = (value &&
typeof value.then === 'function') ?
await value :
value;
if (res === null) {
reading = false;
throw new ERR_STREAM_NULL_VALUES();
}
if (readable.push(res)) {
continue;
}
reading = false;
} catch (err) {
readable.destroy(err);
}
break;
}
}
async function nextAsync() {
for (;;) {
try {
const { value, done } = await iterator.next();
if (done) {
readable.push(null);
return;
}
if (value === null) {
reading = false;
throw new ERR_STREAM_NULL_VALUES();
}
if (readable.push(value)) {
continue;
}
reading = false;
} catch (err) { } catch (err) {
readable.destroy(err); readable.destroy(err);
} }