diff --git a/doc/api/stream.md b/doc/api/stream.md index a880bae5bbe31d..ad13e8ee6ade38 100644 --- a/doc/api/stream.md +++ b/doc/api/stream.md @@ -1642,6 +1642,10 @@ readable.on('data', (chunk) => { }); ``` +Calling `Readable.from(string)` or `Readable.from(buffer)` will not have +the strings or buffers be iterated to match the other streams semantics +for performance reasons. + ## API for Stream Implementers diff --git a/lib/internal/streams/from.js b/lib/internal/streams/from.js index a060638df2dcfe..474db60b8930a2 100644 --- a/lib/internal/streams/from.js +++ b/lib/internal/streams/from.js @@ -4,6 +4,7 @@ const { Symbol, SymbolIterator } = primordials; +const { Buffer } = require('buffer'); const { ERR_INVALID_ARG_TYPE @@ -11,6 +12,17 @@ const { function from(Readable, iterable, opts) { let iterator; + if (typeof iterable === 'string' || iterable instanceof Buffer) { + return new Readable({ + objectMode: true, + ...opts, + read() { + this.push(iterable); + this.push(null); + } + }); + } + if (iterable && iterable[Symbol.asyncIterator]) iterator = iterable[Symbol.asyncIterator](); else if (iterable && iterable[SymbolIterator]) diff --git a/test/parallel/test-readable-from.js b/test/parallel/test-readable-from.js index e43cec6b1e5246..6cc9216a306378 100644 --- a/test/parallel/test-readable-from.js +++ b/test/parallel/test-readable-from.js @@ -56,13 +56,23 @@ async function toReadablePromises() { async function toReadableString() { const stream = Readable.from('abc'); - const expected = ['a', 'b', 'c']; + const expected = ['abc']; for await (const chunk of stream) { strictEqual(chunk, expected.shift()); } } +async function toReadableBuffer() { + const stream = Readable.from(Buffer.from('abc')); + + const expected = ['abc']; + + for await (const chunk of stream) { + strictEqual(chunk.toString(), expected.shift()); + } +} + async function toReadableOnData() { async function* generate() { yield 'a'; @@ -154,6 +164,7 @@ Promise.all([ toReadableSyncIterator(), toReadablePromises(), toReadableString(), + toReadableBuffer(), toReadableOnData(), toReadableOnDataNonObject(), destroysTheStreamWhenThrowing(),