Skip to content

Commit 450e495

Browse files
committed
fix: flatMap should accept any (async)iterable
1 parent 153f80e commit 450e495

File tree

6 files changed

+102
-18
lines changed

6 files changed

+102
-18
lines changed

README.md

Lines changed: 45 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,51 @@
33
Strict and wrapper version implementation for
44
https://github.com/tc39/proposal-iterator-helpers.
55

6+
## Usage
7+
8+
```ts
9+
import {
10+
asyncIteratorFrom,
11+
iteratorFrom,
12+
wrapAsyncIterator,
13+
wrapIterator,
14+
} from "./mod.ts";
15+
16+
function* naturals() {
17+
let i = 0;
18+
while (true) {
19+
yield i;
20+
i += 1;
21+
}
22+
}
23+
24+
const arr1 = wrapIterator(naturals())
25+
.filter((n) => n % 2 === 1) // filter odd numbers
26+
.map((n) => n ** 2) // square numbers
27+
.flatMap((n) => [n, n]) // twice each numbers
28+
.take(10) // cut up to 10 items
29+
.toArray(); // evaluate and collect items into array
30+
console.log(arr1); // [1, 1, 9, 9, 25, 25, 49, 49, 81, 81]
31+
32+
const arr2 = await wrapAsyncIterator(asyncIteratorFrom(naturals()))
33+
.filter(async (n) => {
34+
const res = await fetch(`https://api.isevenapi.xyz/api/iseven/${n}/`);
35+
if (!res.body) throw new Error("No body");
36+
const raw = Uint8Array.from(
37+
await wrapAsyncIterator(asyncIteratorFrom(res.body))
38+
.flatMap((e) => e)
39+
.toArray(),
40+
);
41+
const obj = JSON.parse(new TextDecoder().decode(raw));
42+
return obj.iseven;
43+
}) // filter even numbers
44+
.map((n) => n ** 2) // square numbers
45+
.flatMap((n) => [n, n]) // twice each numbers
46+
.take(10) // cut up to 10 items
47+
.toArray(); // evaluate and collect items into array
48+
console.log(arr2); // [0, 0, 4, 4, 16, 16, 36, 36, 64, 64]
49+
```
50+
651
## Goals
752

853
- Implement all proposed features with wrapper API.

lib/wrap_async_iterator.ts

Lines changed: 25 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,13 @@ export type WrappedAsyncIterator<T> = {
1515
drop: (limit: number) => WrappedAsyncIterator<T>;
1616
asIndexedPairs: () => WrappedAsyncIterator<[number, T]>;
1717
flatMap: <U>(
18-
mapperFn: (t: T) => U | U[] | Promise<U | U[]>,
18+
mapperFn: (
19+
t: T,
20+
) =>
21+
| U
22+
| Iterable<U>
23+
| AsyncIterable<U>
24+
| Promise<U | Iterable<U> | AsyncIterable<U>>,
1925
) => WrappedAsyncIterator<U>;
2026
reduce: {
2127
<U>(
@@ -114,11 +120,25 @@ export const wrapAsyncIterator = <T>(
114120
}
115121
return wrapAsyncIterator((async function* () {
116122
for await (const v of { [Symbol.asyncIterator]: () => ite }) {
117-
const arr = await mapperFn(v);
118-
if (Array.isArray(arr)) {
119-
for (const e of arr) yield e;
123+
const inner = await mapperFn(v);
124+
const getAsync = (inner as any)[Symbol.asyncIterator];
125+
if (getAsync != null) {
126+
if (typeof getAsync !== "function") {
127+
throw new TypeError(`${getAsync} is not a function`);
128+
}
129+
const iteInner = getAsync.call(inner);
130+
for await (const vInner of iteInner) yield vInner;
120131
} else {
121-
yield arr;
132+
const getSync = (inner as any)[Symbol.iterator];
133+
if (getSync != null) {
134+
if (typeof getSync !== "function") {
135+
throw new TypeError(`${getSync} is not a function`);
136+
}
137+
const iteInner = getSync.call(inner);
138+
for (const vInner of iteInner) yield vInner;
139+
} else {
140+
yield inner;
141+
}
122142
}
123143
}
124144
})());

lib/wrap_iterator.ts

Lines changed: 10 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,7 @@ export type WrappedIterator<T> = {
1515
drop: (limit: number) => WrappedIterator<T>;
1616
asIndexedPairs: () => WrappedIterator<[number, T]>;
1717
flatMap: <U>(
18-
mapperFn: (t: T) => U | U[],
18+
mapperFn: (t: T) => U | Iterable<U>,
1919
) => WrappedIterator<U>;
2020
reduce: {
2121
<U>(
@@ -112,11 +112,16 @@ export const wrapIterator = <T>(ite: Iterator<T>): WrappedIterator<T> => {
112112
}
113113
return wrapIterator((function* () {
114114
for (const v of { [Symbol.iterator]: () => ite }) {
115-
const arr = mapperFn(v);
116-
if (Array.isArray(arr)) {
117-
for (const e of arr) yield e;
115+
const inner = mapperFn(v);
116+
const getSync = (inner as any)[Symbol.iterator];
117+
if (getSync != null) {
118+
if (typeof getSync !== "function") {
119+
throw new TypeError(`${getSync} is not a function`);
120+
}
121+
const iteInner = getSync.call(inner);
122+
for (const vInner of iteInner) yield vInner;
118123
} else {
119-
yield arr;
124+
yield inner;
120125
}
121126
}
122127
})());

mod.ts

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
export * from "./lib/async_iterator_from.spec.ts";
2-
export * from "./lib/iterator_from.spec.ts";
3-
export * from "./lib/wrap_async_iterator.spec.ts";
4-
export * from "./lib/wrap_iterator.spec.ts";
1+
export * from "./lib/async_iterator_from.ts";
2+
export * from "./lib/iterator_from.ts";
3+
export * from "./lib/wrap_async_iterator.ts";
4+
export * from "./lib/wrap_iterator.ts";

test/unit/wrap_async_iterator.spec.ts

Lines changed: 9 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -213,10 +213,17 @@ Deno.test({
213213
name: "AsyncIterator.prototype.flatMap",
214214
async fn() {
215215
asserts.assertEquals(
216-
await wrapAsyncIterator(asyncIteratorFrom([1, 2, [3, 4], [5, [6, 7], 8]]))
216+
await wrapAsyncIterator(asyncIteratorFrom([
217+
1,
218+
2,
219+
[3, 4],
220+
[5, [6, 7], 8],
221+
// any (async)iterable should be accepted
222+
Uint8Array.from([9, 10, 11]),
223+
]))
217224
.flatMap<number | number[]>((e) => typeof e === "number" ? e * 100 : e)
218225
.toArray(),
219-
[100, 200, 3, 4, 5, [6, 7], 8],
226+
[100, 200, 3, 4, 5, [6, 7], 8, 9, 10, 11],
220227
);
221228
asserts.assertThrows(
222229
() => wrapAsyncIterator(asyncIteratorFrom([1, 2, 3])).flatMap(1 as any),

test/unit/wrap_iterator.spec.ts

Lines changed: 9 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -211,10 +211,17 @@ Deno.test({
211211
name: "Iterator.prototype.flatMap",
212212
fn() {
213213
asserts.assertEquals(
214-
wrapIterator(iteratorFrom([1, 2, [3, 4], [5, [6, 7], 8]]))
214+
wrapIterator(iteratorFrom([
215+
1,
216+
2,
217+
[3, 4],
218+
[5, [6, 7], 8],
219+
// any iterable should be accepted
220+
Uint8Array.from([9, 10, 11]),
221+
]))
215222
.flatMap<number | number[]>((e) => typeof e === "number" ? e * 100 : e)
216223
.toArray(),
217-
[100, 200, 3, 4, 5, [6, 7], 8],
224+
[100, 200, 3, 4, 5, [6, 7], 8, 9, 10, 11],
218225
);
219226
asserts.assertThrows(
220227
() => wrapIterator(iteratorFrom([1, 2, 3])).flatMap(1 as any),

0 commit comments

Comments
 (0)