diff --git a/packages/it-filter/README.md b/packages/it-filter/README.md index 91ce989a..5a57f7fd 100644 --- a/packages/it-filter/README.md +++ b/packages/it-filter/README.md @@ -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)) diff --git a/packages/it-filter/package.json b/packages/it-filter/package.json index 4bcebd52..de8fdcc3 100644 --- a/packages/it-filter/package.json +++ b/packages/it-filter/package.json @@ -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" diff --git a/packages/it-filter/src/index.ts b/packages/it-filter/src/index.ts index 471ce25b..d5507138 100644 --- a/packages/it-filter/src/index.ts +++ b/packages/it-filter/src/index.ts @@ -1,11 +1,60 @@ +import peek from 'it-peekable' + +function isAsyncIterable (thing: any): thing is AsyncIterable { + return thing[Symbol.asyncIterator] != null +} /** * Filters the passed (async) iterable by using the filter function */ -export default async function * filter (source: AsyncIterable | Iterable, fn: (val: T) => boolean | Promise): AsyncGenerator { - for await (const entry of source) { - if (await fn(entry)) { - yield entry - } +function filter (source: Iterable, fn: (val: T) => Promise): AsyncGenerator +function filter (source: Iterable, fn: (val: T) => boolean): Generator +function filter (source: Iterable | AsyncIterable, fn: (val: T) => boolean | Promise): AsyncGenerator +function filter (source: Iterable | AsyncIterable, fn: (val: T) => boolean | Promise): Generator | AsyncGenerator { + 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 diff --git a/packages/it-filter/test/index.spec.ts b/packages/it-filter/test/index.spec.ts index 1efebf08..762f190c 100644 --- a/packages/it-filter/test/index.spec.ts +++ b/packages/it-filter/test/index.spec.ts @@ -2,20 +2,47 @@ import { expect } from 'aegir/chai' import all from 'it-all' import filter from '../src/index.js' +function * values (): Generator { + yield * [0, 1, 2, 3, 4] +} + +async function * asyncValues (): AsyncGenerator { + 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]) }) })