Skip to content

Commit

Permalink
feat: AsyncIterable2 (experimental)
Browse files Browse the repository at this point in the history
  • Loading branch information
kirillgroshkov committed Jan 2, 2024
1 parent fe25e8f commit 5a00bc4
Show file tree
Hide file tree
Showing 4 changed files with 171 additions and 0 deletions.
29 changes: 29 additions & 0 deletions src/array/range.ts
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
import { AsyncIterable2 } from '../iter/asyncIterable2'
import { Iterable2 } from '../iter/iterable2'

/* eslint-disable no-redeclare, unicorn/no-new-array */
Expand Down Expand Up @@ -44,3 +45,31 @@ export function _rangeIterable(fromIncl: number, toExcl?: number, step = 1): Ite
},
})
}

/**
* Like _range, but returns an AsyncIterable2.
*/
export function _rangeAsyncIterable(toExcl: number): AsyncIterable2<number>
export function _rangeAsyncIterable(
fromIncl: number,
toExcl: number,
step?: number,
): AsyncIterable2<number>
export function _rangeAsyncIterable(
fromIncl: number,
toExcl?: number,
step = 1,
): AsyncIterable2<number> {
if (toExcl === undefined) {
toExcl = fromIncl
fromIncl = 0
}

return AsyncIterable2.of({
async *[Symbol.asyncIterator]() {
for (let i = fromIncl; i < toExcl!; i += step) {
yield i
}
},
})
}
1 change: 1 addition & 0 deletions src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -61,6 +61,7 @@ export * from './string/safeJsonStringify'
export * from './promise/pQueue'
export * from './promise/abortable'
export * from './iter/iterable2'
export * from './iter/asyncIterable2'
export * from './math/stack.util'
export * from './string/leven'
export * from './datetime/localDate'
Expand Down
32 changes: 32 additions & 0 deletions src/iter/asyncIterable2.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
import { _rangeAsyncIterable } from '../array/range'
import { AsyncIterable2 } from './asyncIterable2'

test('asyncIterable2', async () => {
expect(await _rangeAsyncIterable(3).toArray()).toEqual([0, 1, 2])

expect(await _rangeAsyncIterable(1, 4).find(v => v % 2 === 0)).toBe(2)
expect(await _rangeAsyncIterable(1, 4).some(v => v % 2 === 0)).toBe(true)
expect(await _rangeAsyncIterable(1, 4).some(v => v % 2 === -1)).toBe(false)
expect(await _rangeAsyncIterable(1, 4).every(v => v % 2 === 0)).toBe(false)
expect(await _rangeAsyncIterable(1, 4).every(v => v > 0)).toBe(true)

expect(
await _rangeAsyncIterable(1, 4)
.filter(v => v % 2 === 1)
.toArray(),
).toEqual([1, 3])

expect(
await _rangeAsyncIterable(1, 4)
.map(v => v * 2)
.toArray(),
).toEqual([2, 4, 6])

const a: number[] = []
await _rangeAsyncIterable(1, 4).forEach(v => a.push(v))
expect(a).toEqual([1, 2, 3])

expect(await AsyncIterable2.ofIterable([]).toArray()).toEqual([])
expect(await AsyncIterable2.empty().toArray()).toEqual([])
expect(await AsyncIterable2.ofIterable([3, 4]).toArray()).toEqual([3, 4])
})
109 changes: 109 additions & 0 deletions src/iter/asyncIterable2.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,109 @@
import { Promisable } from '../typeFest'
import { AbortableAsyncMapper, AbortableAsyncPredicate, END, SKIP } from '../types'

/**
* Similar to Iterable2, but for AsyncIterable.
*
* AsyncIterable2 is a wrapper around AsyncIterable that implements "Iterator Helpers proposal":
* https://github.com/tc39/proposal-iterator-helpers
*
* AsyncIterable2 can be removed after the proposal is widely implemented in Node & browsers.
*
* @experimental
*/
export class AsyncIterable2<T> implements AsyncIterable<T> {
private constructor(private it: AsyncIterable<T>) {}

static of<T>(it: AsyncIterable<T>): AsyncIterable2<T> {
return new AsyncIterable2(it)
}

static ofIterable<T>(it: Iterable<T>): AsyncIterable2<T> {
return new AsyncIterable2<T>({
async *[Symbol.asyncIterator]() {
yield* it
},
})
}

static empty<T>(): AsyncIterable2<T> {
return new AsyncIterable2<T>({
async *[Symbol.asyncIterator]() {},
})
}

[Symbol.asyncIterator](): AsyncIterator<T> {
return this.it[Symbol.asyncIterator]()
}

async toArray(): Promise<T[]> {
// todo: Array.fromAsync is not yet available, use that when it's ready
// return await Array.fromAsync(this.it)

const res: T[] = []
for await (const item of this.it) {
res.push(item)
}
return res
}

async forEach(cb: (v: T, i: number) => Promisable<any | typeof END>): Promise<void> {
let i = 0
for await (const v of this.it) {
if ((await cb(v, i++)) === END) return
}
}

async some(cb: AbortableAsyncPredicate<T>): Promise<boolean> {
return !!(await this.find(cb))
}

async every(cb: AbortableAsyncPredicate<T>): Promise<boolean> {
let i = 0
for await (const v of this.it) {
const r = await cb(v, i++)
if (r === END || !r) return false
}
return true
}

async find(cb: AbortableAsyncPredicate<T>): Promise<T | undefined> {
let i = 0
for await (const v of this.it) {
const r = await cb(v, i++)
if (r === END) return
if (r) return v
}
}

filter(cb: AbortableAsyncPredicate<T>): AsyncIterable2<T> {
const { it } = this

return new AsyncIterable2<T>({
async *[Symbol.asyncIterator]() {
let i = 0
for await (const v of it) {
const r = await cb(v, i++)
if (r === END) return
if (r) yield v
}
},
})
}

map<OUT>(mapper: AbortableAsyncMapper<T, OUT>): AsyncIterable2<OUT> {
const { it } = this

return new AsyncIterable2<OUT>({
async *[Symbol.asyncIterator]() {
let i = 0
for await (const v of it) {
const r = await mapper(v, i++)
if (r === END) return
if (r === SKIP) continue
yield r
}
},
})
}
}

0 comments on commit 5a00bc4

Please sign in to comment.