Skip to content

Commit 48ff0a1

Browse files
committed
stream: compose with async functions
Enables async function support for stream.compose. PR-URL: #39435 Reviewed-By: Matteo Collina <matteo.collina@gmail.com> Reviewed-By: Benjamin Gruenbaum <benjamingr@gmail.com> Reviewed-By: James M Snell <jasnell@gmail.com>
1 parent 5100c3c commit 48ff0a1

File tree

2 files changed

+52
-27
lines changed

2 files changed

+52
-27
lines changed

doc/api/stream.md

Lines changed: 50 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1861,7 +1861,7 @@ failure, this can cause event listener leaks and swallowed errors.
18611861
added: REPLACEME
18621862
-->
18631863

1864-
* `streams` {Stream[]}
1864+
* `streams` {Stream[]|Iterable[]|AsyncIterable[]|Function[]}
18651865
* Returns: {stream.Duplex}
18661866

18671867
Combines two or more streams into a `Duplex` stream that writes to the
@@ -1875,6 +1875,9 @@ when passing streams to `stream.pipeline`, typically the first stream is
18751875
a readable stream and the last a writable stream, forming a closed
18761876
circuit.
18771877

1878+
If passed a `Function` it must be a factory method taking a `source`
1879+
`Iterable`.
1880+
18781881
```mjs
18791882
import { compose, Transform } from 'stream';
18801883

@@ -1884,11 +1887,11 @@ const removeSpaces = new Transform({
18841887
}
18851888
});
18861889

1887-
const toUpper = new Transform({
1888-
transform(chunk, encoding, callback) {
1889-
callback(null, String(chunk).toUpperCase());
1890+
async function* toUpper(source) {
1891+
for await (const chunk of source) {
1892+
yield String(chunk).toUpperCase();
18901893
}
1891-
});
1894+
}
18921895

18931896
let res = '';
18941897
for await (const buf of compose(removeSpaces, toUpper).end('hello world')) {
@@ -1898,6 +1901,48 @@ for await (const buf of compose(removeSpaces, toUpper).end('hello world')) {
18981901
console.log(res); // prints 'HELLOWORLD'
18991902
```
19001903

1904+
`stream.compose` can be used to convert async iterables, generators and
1905+
functions into streams.
1906+
1907+
* `AsyncIterable` converts into a readable `Duplex`. Cannot yield
1908+
`null`.
1909+
* `AsyncGeneratorFunction` converts into a readable/writable transform `Duplex`.
1910+
Must take a source `AsyncIterable` as first parameter. Cannot yield
1911+
`null`.
1912+
* `AsyncFunction` converts into a writable `Duplex`. Must return
1913+
either `null` or `undefined`.
1914+
1915+
```mjs
1916+
import { compose } from 'stream';
1917+
import { finished } from 'stream/promises';
1918+
1919+
// Convert AsyncIterable into readable Duplex.
1920+
const s1 = compose(async function*() {
1921+
yield 'Hello';
1922+
yield 'World';
1923+
}());
1924+
1925+
// Convert AsyncGenerator into transform Duplex.
1926+
const s2 = compose(async function*(source) {
1927+
for await (const chunk of source) {
1928+
yield String(chunk).toUpperCase();
1929+
}
1930+
});
1931+
1932+
let res = '';
1933+
1934+
// Convert AsyncFunction into writable Duplex.
1935+
const s3 = compose(async function(source) {
1936+
for await (const chunk of source) {
1937+
res += chunk;
1938+
}
1939+
});
1940+
1941+
await finished(compose(s1, s2, s3));
1942+
1943+
console.log(res); // prints 'HELLOWORLD'
1944+
```
1945+
19011946
### `stream.Readable.from(iterable, [options])`
19021947
<!-- YAML
19031948
added:

lib/stream.js

Lines changed: 2 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -30,16 +30,10 @@ const {
3030
} = require('internal/util');
3131

3232
const pipeline = require('internal/streams/pipeline');
33-
const _compose = require('internal/streams/compose');
33+
const compose = require('internal/streams/compose');
3434
const { destroyer } = require('internal/streams/destroy');
3535
const eos = require('internal/streams/end-of-stream');
3636
const internalBuffer = require('internal/buffer');
37-
const { isNodeStream } = require('internal/streams/utils');
38-
const {
39-
codes: {
40-
ERR_INVALID_ARG_VALUE,
41-
},
42-
} = require('internal/errors');
4337

4438
const promises = require('stream/promises');
4539

@@ -54,21 +48,7 @@ const { addAbortSignal } = require('internal/streams/add-abort-signal');
5448
Stream.addAbortSignal = addAbortSignal;
5549
Stream.finished = eos;
5650
Stream.destroy = destroyer;
57-
58-
Stream.compose = function compose(...streams) {
59-
// TODO (ronag): Remove this once async function API
60-
// has been discussed.
61-
for (let n = 0; n < streams.length; ++n) {
62-
if (!isNodeStream(streams[n])) {
63-
throw new ERR_INVALID_ARG_VALUE(
64-
`streams[${n}]`,
65-
streams[n],
66-
'must be stream'
67-
);
68-
}
69-
}
70-
return _compose(...streams);
71-
};
51+
Stream.compose = compose;
7252

7353
ObjectDefineProperty(Stream, 'promises', {
7454
configurable: true,

0 commit comments

Comments
 (0)