Skip to content

Commit cdd84b4

Browse files
authored
fix!: return iterators from synchronous sources (#58)
Applies the same changes from #55 to it-filter BREAKING CHANGE: if you pass a synchronous iterator and a synchronous filter function it will return a synchronous generator in response
1 parent bf14176 commit cdd84b4

File tree

4 files changed

+108
-12
lines changed

4 files changed

+108
-12
lines changed

packages/it-filter/README.md

Lines changed: 19 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -33,10 +33,27 @@ Loading this module through a script tag will make it's exports available as `It
3333
import all from 'it-all'
3434
import filter from 'it-filter'
3535

36-
// This can also be an iterator, async iterator, generator, etc
36+
// This can also be an iterator, generator, etc
3737
const values = [0, 1, 2, 3, 4]
3838

39-
const fn = val => val > 2 // Return boolean or promise of boolean to keep item
39+
const fn = val => val > 2 // Return boolean to keep item
40+
41+
const arr = all(filter(values, fn))
42+
43+
console.info(arr) // 3, 4
44+
```
45+
46+
Async sources and filter functions must be awaited:
47+
48+
```javascript
49+
import all from 'it-all'
50+
import filter from 'it-filter'
51+
52+
const values = async function * () {
53+
yield * [0, 1, 2, 3, 4]
54+
}
55+
56+
const fn = async val => val > 2 // Return boolean or promise of boolean to keep item
4057

4158
const arr = await all(filter(values, fn))
4259

packages/it-filter/package.json

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -134,6 +134,9 @@
134134
"test:firefox-webworker": "aegir test -t webworker -- --browser firefox",
135135
"release": "aegir release"
136136
},
137+
"dependencies": {
138+
"it-peekable": "^3.0.0"
139+
},
137140
"devDependencies": {
138141
"aegir": "^38.1.7",
139142
"it-all": "^3.0.0"

packages/it-filter/src/index.ts

Lines changed: 54 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,60 @@
1+
import peek from 'it-peekable'
2+
3+
function isAsyncIterable <T> (thing: any): thing is AsyncIterable<T> {
4+
return thing[Symbol.asyncIterator] != null
5+
}
16

27
/**
38
* Filters the passed (async) iterable by using the filter function
49
*/
5-
export default async function * filter <T> (source: AsyncIterable<T> | Iterable<T>, fn: (val: T) => boolean | Promise<boolean>): AsyncGenerator<T, void, undefined> {
6-
for await (const entry of source) {
7-
if (await fn(entry)) {
8-
yield entry
9-
}
10+
function filter <T> (source: Iterable<T>, fn: (val: T) => Promise<boolean>): AsyncGenerator<T, void, undefined>
11+
function filter <T> (source: Iterable<T>, fn: (val: T) => boolean): Generator<T, void, undefined>
12+
function filter <T> (source: Iterable<T> | AsyncIterable<T>, fn: (val: T) => boolean | Promise<boolean>): AsyncGenerator<T, void, undefined>
13+
function filter <T> (source: Iterable<T> | AsyncIterable<T>, fn: (val: T) => boolean | Promise<boolean>): Generator<T, void, undefined> | AsyncGenerator<T, void, undefined> {
14+
if (isAsyncIterable(source)) {
15+
return (async function * () {
16+
for await (const entry of source) {
17+
if (await fn(entry)) {
18+
yield entry
19+
}
20+
}
21+
})()
22+
}
23+
24+
// if mapping function returns a promise we have to return an async generator
25+
const peekable = peek(source)
26+
const { value, done } = peekable.next()
27+
28+
if (done === true) {
29+
return (function * () {}())
30+
}
31+
32+
const res = fn(value)
33+
34+
// @ts-expect-error .then is not present on O
35+
if (typeof res.then === 'function') {
36+
return (async function * () {
37+
if (await res) {
38+
yield value
39+
}
40+
41+
for await (const entry of peekable) {
42+
if (await fn(entry)) {
43+
yield entry
44+
}
45+
}
46+
})()
1047
}
48+
49+
const func = fn as (val: T) => boolean
50+
51+
return (function * () {
52+
for (const entry of source) {
53+
if (func(entry)) {
54+
yield entry
55+
}
56+
}
57+
})()
1158
}
59+
60+
export default filter

packages/it-filter/test/index.spec.ts

Lines changed: 32 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -2,20 +2,47 @@ import { expect } from 'aegir/chai'
22
import all from 'it-all'
33
import filter from '../src/index.js'
44

5+
function * values (): Generator<number, void, undefined> {
6+
yield * [0, 1, 2, 3, 4]
7+
}
8+
9+
async function * asyncValues (): AsyncGenerator<number, void, undefined> {
10+
yield * values()
11+
}
12+
513
describe('it-filter', () => {
614
it('should filter all values greater than 2', async () => {
7-
const values = [0, 1, 2, 3, 4]
15+
const res = all(filter(values(), val => val > 2))
16+
17+
expect(res[Symbol.iterator]).to.be.ok()
18+
expect(res).to.deep.equal([3, 4])
19+
})
820

9-
const res = await all(filter(values, val => val > 2))
21+
it('should filter all values greater than 2 with a promise', () => {
22+
const res = all(filter(values(), val => val > 2))
1023

24+
expect(res[Symbol.iterator]).to.be.ok()
1125
expect(res).to.deep.equal([3, 4])
1226
})
1327

1428
it('should filter all values greater than 2 with a promise', async () => {
15-
const values = [0, 1, 2, 3, 4]
29+
const res = filter(values(), async val => val > 2)
1630

17-
const res = await all(filter(values, async val => val > 2))
31+
expect(res[Symbol.asyncIterator]).to.be.ok()
32+
await expect(all(res)).to.eventually.deep.equal([3, 4])
33+
})
1834

19-
expect(res).to.deep.equal([3, 4])
35+
it('should filter all async values greater than 2', async () => {
36+
const res = filter(asyncValues(), val => val > 2)
37+
38+
expect(res[Symbol.asyncIterator]).to.be.ok()
39+
await expect(all(res)).to.eventually.deep.equal([3, 4])
40+
})
41+
42+
it('should filter all async values greater than 2 with a promise', async () => {
43+
const res = filter(asyncValues(), async val => val > 2)
44+
45+
expect(res[Symbol.asyncIterator]).to.be.ok()
46+
await expect(all(res)).to.eventually.deep.equal([3, 4])
2047
})
2148
})

0 commit comments

Comments
 (0)