Skip to content

Commit

Permalink
feat: pass index to callback functions
Browse files Browse the repository at this point in the history
Pass the current index to filter/foreach/map and reduce functions
similar to Array.prototype functions.
  • Loading branch information
achingbrain committed Apr 24, 2024
1 parent be59731 commit 847dbb2
Show file tree
Hide file tree
Showing 8 changed files with 261 additions and 86 deletions.
24 changes: 13 additions & 11 deletions packages/it-filter/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@
* // This can also be an iterator, generator, etc
* const values = [0, 1, 2, 3, 4]
*
* const fn = val => val > 2 // Return boolean to keep item
* const fn = (val, index) => val > 2 // Return boolean to keep item
*
* const arr = all(filter(values, fn))
*
Expand All @@ -29,7 +29,7 @@
* yield * [0, 1, 2, 3, 4]
* }
*
* const fn = async val => val > 2 // Return boolean or promise of boolean to keep item
* const fn = async val => (val, index) > 2 // Return boolean or promise of boolean to keep item
*
* const arr = await all(filter(values, fn))
*
Expand All @@ -46,14 +46,16 @@ function isAsyncIterable <T> (thing: any): thing is AsyncIterable<T> {
/**
* Filters the passed (async) iterable by using the filter function
*/
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> {
function filter <T> (source: Iterable<T>, fn: (val: T, index: number) => Promise<boolean>): AsyncGenerator<T, void, undefined>
function filter <T> (source: Iterable<T>, fn: (val: T, index: number) => boolean): Generator<T, void, undefined>
function filter <T> (source: Iterable<T> | AsyncIterable<T>, fn: (val: T, index: number) => boolean | Promise<boolean>): AsyncGenerator<T, void, undefined>
function filter <T> (source: Iterable<T> | AsyncIterable<T>, fn: (val: T, index: number) => boolean | Promise<boolean>): Generator<T, void, undefined> | AsyncGenerator<T, void, undefined> {
let index = 0

if (isAsyncIterable(source)) {
return (async function * () {
for await (const entry of source) {
if (await fn(entry)) {
if (await fn(entry, index++)) {
yield entry
}
}
Expand All @@ -68,7 +70,7 @@ function filter <T> (source: Iterable<T> | AsyncIterable<T>, fn: (val: T) => boo
return (function * () {}())
}

const res = fn(value)
const res = fn(value, index++)

// @ts-expect-error .then is not present on O
if (typeof res.then === 'function') {
Expand All @@ -78,22 +80,22 @@ function filter <T> (source: Iterable<T> | AsyncIterable<T>, fn: (val: T) => boo
}

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

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

return (function * () {
if (res === true) {
yield value
}

for (const entry of peekable) {
if (func(entry)) {
if (func(entry, index++)) {
yield entry
}
}
Expand Down
46 changes: 42 additions & 4 deletions packages/it-filter/test/index.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,12 +2,12 @@ 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]
function * values (vals: number[] = [0, 1, 2, 3, 4]): Generator<number, void, undefined> {
yield * vals
}

async function * asyncValues (): AsyncGenerator<number, void, undefined> {
yield * values()
async function * asyncValues (vals: number[] = [0, 1, 2, 3, 4]): AsyncGenerator<number, void, undefined> {
yield * values(vals)
}

describe('it-filter', () => {
Expand Down Expand Up @@ -52,4 +52,42 @@ describe('it-filter', () => {
expect(res[Symbol.asyncIterator]).to.be.ok()
await expect(all(res)).to.eventually.deep.equal([3, 4])
})

it('should filter values with indexes', async () => {
const vals = [4, 3, 2, 1, 0]
const callbackArgs: any[] = []
const gen = filter(values(vals), (...args: any[]) => {
callbackArgs.push(args)
return true
})
expect(gen[Symbol.iterator]).to.be.ok()

const results = all(gen)
expect(results).to.have.lengthOf(vals.length)
expect(callbackArgs).to.have.lengthOf(vals.length)

vals.forEach((value, index) => {
expect(callbackArgs).to.have.nested.property(`[${index}][0]`, value)
expect(callbackArgs).to.have.nested.property(`[${index}][1]`, index)
})
})

it('should filter async values with indexes', async () => {
const vals = [4, 3, 2, 1, 0]
const callbackArgs: any[] = []
const gen = filter(asyncValues(vals), (...args: any[]) => {
callbackArgs.push(args)
return true
})
expect(gen[Symbol.asyncIterator]).to.be.ok()

const results = await all(gen)
expect(results).to.have.lengthOf(vals.length)
expect(callbackArgs).to.have.lengthOf(vals.length)

vals.forEach((value, index) => {
expect(callbackArgs).to.have.nested.property(`[${index}][0]`, value)
expect(callbackArgs).to.have.nested.property(`[${index}][1]`, index)
})
})
})
24 changes: 13 additions & 11 deletions packages/it-foreach/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@
* // This can also be an iterator, generator, etc
* const values = [0, 1, 2, 3, 4]
*
* // prints 0, 1, 2, 3, 4
* // prints [0, 0], [1, 1], [2, 2], [3, 3], [4, 4]
* const arr = drain(
* each(values, console.info)
* )
Expand All @@ -32,7 +32,7 @@
* yield * [0, 1, 2, 3, 4]
* }
*
* // prints 0, 1, 2, 3, 4
* // prints [0, 0], [1, 1], [2, 2], [3, 3], [4, 4]
* const arr = await drain(
* each(values(), console.info)
* )
Expand All @@ -52,14 +52,16 @@ function isPromise <T = unknown> (thing: any): thing is Promise<T> {
/**
* Invokes the passed function for each item in an iterable
*/
function forEach <T> (source: Iterable<T>, fn: (thing: T) => Promise<void>): AsyncGenerator<T, void, undefined>
function forEach <T> (source: Iterable<T>, fn: (thing: T) => void): Generator<T, void, undefined>
function forEach <T> (source: Iterable<T> | AsyncIterable<T>, fn: (thing: T) => void | Promise<void>): AsyncGenerator<T, void, undefined>
function forEach <T> (source: Iterable<T> | AsyncIterable<T>, fn: (thing: T) => void | Promise<void>): AsyncGenerator<T, void, undefined> | Generator<T, void, undefined> {
function forEach <T> (source: Iterable<T>, fn: (thing: T, index: number) => Promise<void>): AsyncGenerator<T, void, undefined>
function forEach <T> (source: Iterable<T>, fn: (thing: T, index: number) => void): Generator<T, void, undefined>
function forEach <T> (source: Iterable<T> | AsyncIterable<T>, fn: (thing: T, index: number) => void | Promise<void>): AsyncGenerator<T, void, undefined>
function forEach <T> (source: Iterable<T> | AsyncIterable<T>, fn: (thing: T, index: number) => void | Promise<void>): AsyncGenerator<T, void, undefined> | Generator<T, void, undefined> {
let index = 0

if (isAsyncIterable(source)) {
return (async function * () {
for await (const val of source) {
const res = fn(val)
const res = fn(val, index++)

if (isPromise(res)) {
await res
Expand All @@ -78,14 +80,14 @@ function forEach <T> (source: Iterable<T> | AsyncIterable<T>, fn: (thing: T) =>
return (function * () {}())
}

const res = fn(value)
const res = fn(value, index++)

if (typeof res?.then === 'function') {
return (async function * () {
yield value

for await (const val of peekable) {
const res = fn(val)
const res = fn(val, index++)

if (isPromise(res)) {
await res
Expand All @@ -96,13 +98,13 @@ function forEach <T> (source: Iterable<T> | AsyncIterable<T>, fn: (thing: T) =>
})()
}

const func = fn as (val: T) => void
const func = fn as (val: T, index: number) => void

return (function * () {
yield value

for (const val of peekable) {
func(val)
func(val, index++)
yield val
}
})()
Expand Down
93 changes: 75 additions & 18 deletions packages/it-foreach/test/index.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,82 +2,85 @@ import { expect } from 'aegir/chai'
import all from 'it-all'
import forEach from '../src/index.js'

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

async function * asyncValues (vals: number[] = [0, 1, 2, 3, 4]): AsyncGenerator<number, void, undefined> {
yield * values(vals)
}

describe('it-for-each', () => {
it('should iterate over every value', () => {
const values = [0, 1, 2, 3, 4]
const vals = [0, 1, 2, 3, 4]
let sum = 0

const gen = forEach(values, (val) => {
const gen = forEach(values(vals), (val) => {
sum += val
})

expect(gen[Symbol.iterator]).to.be.ok()

const res = all(gen)

expect(res).to.deep.equal(values)
expect(res).to.deep.equal(vals)
expect(10).to.equal(sum)
})

it('should iterate over every async value', async () => {
const values = async function * (): AsyncGenerator<number, void, undefined> {
yield * [0, 1, 2, 3, 4]
}
const vals = [0, 1, 2, 3, 4]
let sum = 0

const gen = forEach(values(), (val) => {
const gen = forEach(asyncValues(vals), (val) => {
sum += val
})

expect(gen[Symbol.asyncIterator]).to.be.ok()

const res = await all(gen)

expect(res).to.deep.equal(await all(values()))
expect(res).to.deep.equal(vals)
expect(10).to.equal(sum)
})

it('should iterate over every value asyncly', async () => {
const values = [0, 1, 2, 3, 4]
const vals = [0, 1, 2, 3, 4]
let sum = 0

const gen = forEach(values, async (val) => {
const gen = forEach(values(), async (val) => {
sum += val
})

expect(gen[Symbol.asyncIterator]).to.be.ok()

const res = await all(gen)

expect(res).to.deep.equal(values)
expect(res).to.deep.equal(vals)
expect(10).to.equal(sum)
})

it('should iterate over every async value asyncly', async () => {
const values = async function * (): AsyncGenerator<number, void, undefined> {
yield * [0, 1, 2, 3, 4]
}
const vals = [0, 1, 2, 3, 4]
let sum = 0

const gen = forEach(values(), async (val) => {
const gen = forEach(asyncValues(), async (val) => {
sum += val
})

expect(gen[Symbol.asyncIterator]).to.be.ok()

const res = await all(gen)

expect(res).to.deep.equal(await all(values()))
expect(res).to.deep.equal(vals)
expect(10).to.equal(sum)
})

it('should abort source', () => {
const values = [0, 1, 2, 3, 4]
let sum = 0
const err = new Error('wat')

try {
all(forEach(values, (val) => {
all(forEach(values(), (val) => {
sum += val

if (val === 3) {
Expand All @@ -91,4 +94,58 @@ describe('it-for-each', () => {
expect(sum).to.equal(6)
}
})

it('should iterate over values with indexes', async () => {
const vals = [4, 3, 2, 1, 0]
const callbackArgs: any[] = []
const gen = forEach(values(vals), (...args: [number]) => {
callbackArgs.push(args)
})
expect(gen[Symbol.iterator]).to.be.ok()

const results = all(gen)
expect(results).to.have.lengthOf(vals.length)
expect(callbackArgs).to.have.lengthOf(vals.length)

vals.forEach((value, index) => {
expect(callbackArgs).to.have.nested.property(`[${index}][0]`, value)
expect(callbackArgs).to.have.nested.property(`[${index}][1]`, index)
})
})

it('should iterate over async values with indexes', async () => {
const vals = [4, 3, 2, 1, 0]
const callbackArgs: any[] = []
const gen = forEach(asyncValues(vals), (...args: any[]) => {
callbackArgs.push(args)
})
expect(gen[Symbol.asyncIterator]).to.be.ok()

const results = await all(gen)
expect(results).to.have.lengthOf(vals.length)
expect(callbackArgs).to.have.lengthOf(vals.length)

vals.forEach((value, index) => {
expect(callbackArgs).to.have.nested.property(`[${index}][0]`, value)
expect(callbackArgs).to.have.nested.property(`[${index}][1]`, index)
})
})

it('should iterate over async values with indexes asyncly', async () => {
const vals = [4, 3, 2, 1, 0]
const callbackArgs: any[] = []
const gen = forEach(asyncValues(vals), async (...args: any[]) => {
callbackArgs.push(args)
})
expect(gen[Symbol.asyncIterator]).to.be.ok()

const results = await all(gen)
expect(results).to.have.lengthOf(vals.length)
expect(callbackArgs).to.have.lengthOf(vals.length)

vals.forEach((value, index) => {
expect(callbackArgs).to.have.nested.property(`[${index}][0]`, value)
expect(callbackArgs).to.have.nested.property(`[${index}][1]`, index)
})
})
})
Loading

0 comments on commit 847dbb2

Please sign in to comment.