Skip to content

Commit

Permalink
fix!: return iterators from synchronous sources (#58)
Browse files Browse the repository at this point in the history
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
  • Loading branch information
achingbrain committed Mar 31, 2023
1 parent bf14176 commit cdd84b4
Show file tree
Hide file tree
Showing 4 changed files with 108 additions and 12 deletions.
21 changes: 19 additions & 2 deletions packages/it-filter/README.md
Expand Up @@ -33,10 +33,27 @@ Loading this module through a script tag will make it's exports available as `It
import all from 'it-all'
import filter from 'it-filter'

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

const fn = val => val > 2 // Return boolean or promise of boolean to keep item
const fn = val => val > 2 // Return boolean to keep item

const arr = all(filter(values, fn))

console.info(arr) // 3, 4
```

Async sources and filter functions must be awaited:

```javascript
import all from 'it-all'
import filter from 'it-filter'

const values = async function * () {
yield * [0, 1, 2, 3, 4]
}

const fn = async val => val > 2 // Return boolean or promise of boolean to keep item

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

Expand Down
3 changes: 3 additions & 0 deletions packages/it-filter/package.json
Expand Up @@ -134,6 +134,9 @@
"test:firefox-webworker": "aegir test -t webworker -- --browser firefox",
"release": "aegir release"
},
"dependencies": {
"it-peekable": "^3.0.0"
},
"devDependencies": {
"aegir": "^38.1.7",
"it-all": "^3.0.0"
Expand Down
59 changes: 54 additions & 5 deletions packages/it-filter/src/index.ts
@@ -1,11 +1,60 @@
import peek from 'it-peekable'

function isAsyncIterable <T> (thing: any): thing is AsyncIterable<T> {
return thing[Symbol.asyncIterator] != null
}

/**
* Filters the passed (async) iterable by using the filter function
*/
export default async function * filter <T> (source: AsyncIterable<T> | Iterable<T>, fn: (val: T) => boolean | Promise<boolean>): AsyncGenerator<T, void, undefined> {
for await (const entry of source) {
if (await fn(entry)) {
yield entry
}
function filter <T> (source: Iterable<T>, fn: (val: T) => Promise<boolean>): AsyncGenerator<T, void, undefined>
function filter <T> (source: Iterable<T>, fn: (val: T) => boolean): Generator<T, void, undefined>
function filter <T> (source: Iterable<T> | AsyncIterable<T>, fn: (val: T) => boolean | Promise<boolean>): AsyncGenerator<T, void, undefined>
function filter <T> (source: Iterable<T> | AsyncIterable<T>, fn: (val: T) => boolean | Promise<boolean>): Generator<T, void, undefined> | AsyncGenerator<T, void, undefined> {
if (isAsyncIterable(source)) {
return (async function * () {
for await (const entry of source) {
if (await fn(entry)) {
yield entry
}
}
})()
}

// if mapping function returns a promise we have to return an async generator
const peekable = peek(source)
const { value, done } = peekable.next()

if (done === true) {
return (function * () {}())
}

const res = fn(value)

// @ts-expect-error .then is not present on O
if (typeof res.then === 'function') {
return (async function * () {
if (await res) {
yield value
}

for await (const entry of peekable) {
if (await fn(entry)) {
yield entry
}
}
})()
}

const func = fn as (val: T) => boolean

return (function * () {
for (const entry of source) {
if (func(entry)) {
yield entry
}
}
})()
}

export default filter
37 changes: 32 additions & 5 deletions packages/it-filter/test/index.spec.ts
Expand Up @@ -2,20 +2,47 @@ import { expect } from 'aegir/chai'
import all from 'it-all'
import filter from '../src/index.js'

function * values (): Generator<number, void, undefined> {
yield * [0, 1, 2, 3, 4]
}

async function * asyncValues (): AsyncGenerator<number, void, undefined> {
yield * values()
}

describe('it-filter', () => {
it('should filter all values greater than 2', async () => {
const values = [0, 1, 2, 3, 4]
const res = all(filter(values(), val => val > 2))

expect(res[Symbol.iterator]).to.be.ok()
expect(res).to.deep.equal([3, 4])
})

const res = await all(filter(values, val => val > 2))
it('should filter all values greater than 2 with a promise', () => {
const res = all(filter(values(), val => val > 2))

expect(res[Symbol.iterator]).to.be.ok()
expect(res).to.deep.equal([3, 4])
})

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

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

expect(res).to.deep.equal([3, 4])
it('should filter all async values greater than 2', async () => {
const res = filter(asyncValues(), val => val > 2)

expect(res[Symbol.asyncIterator]).to.be.ok()
await expect(all(res)).to.eventually.deep.equal([3, 4])
})

it('should filter all async values greater than 2 with a promise', async () => {
const res = filter(asyncValues(), async val => val > 2)

expect(res[Symbol.asyncIterator]).to.be.ok()
await expect(all(res)).to.eventually.deep.equal([3, 4])
})
})

0 comments on commit cdd84b4

Please sign in to comment.