Skip to content

Commit 89a35f8

Browse files
authored
fix(util): update AsyncCollection with better types #2657
1 parent 5b01036 commit 89a35f8

File tree

2 files changed

+59
-25
lines changed

2 files changed

+59
-25
lines changed

src/shared/utilities/asyncCollection.ts

Lines changed: 37 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -9,21 +9,42 @@ import { Coalesce } from './tsUtils'
99
* High-level abstraction over async generator functions of the form `async function*` {@link AsyncGenerator}
1010
*/
1111
export interface AsyncCollection<T> extends AsyncIterable<T> {
12-
/** Flattens the collection 1-level deep */
12+
/**
13+
* Flattens the collection 1-level deep.
14+
*/
1315
flatten(): AsyncCollection<SafeUnboxIterable<T>>
14-
/** Applies a mapping transform to the output generator */
15-
map<U>(fn: (obj: T) => U): AsyncCollection<U>
16-
/** Filters out results which will _not_ be passed on to further transformations. */
17-
filter<U extends T>(predicate: Predicate<T, U>): AsyncCollection<U>
18-
/** Uses only the first 'count' number of values returned by the generator. */
19-
take(count: number): AsyncCollection<T>
20-
/** Converts the collection to a Promise, resolving to an array of all values returned by the generator. */
16+
17+
/**
18+
* Applies a mapping transform to the output generator.
19+
*/
20+
map<U>(fn: (obj: T) => Promise<U> | U): AsyncCollection<U>
21+
22+
/**
23+
* Filters out results which will _not_ be passed on to further transformations.
24+
*/
25+
filter<U extends T>(predicate: (item: T) => item is U): AsyncCollection<U>
26+
filter<U extends T>(predicate: (item: T) => boolean): AsyncCollection<U>
27+
28+
/**
29+
* Uses only the first 'count' number of values returned by the generator.
30+
*/
31+
limit(count: number): AsyncCollection<T>
32+
33+
/**
34+
* Converts the collection to a Promise, resolving to an array of all values returned by the generator.
35+
*/
2136
promise(): Promise<T[]>
22-
/** Converts the collection to a Map, using either a property of the item or a function to select keys. */
37+
38+
/**
39+
* Converts the collection to a Map, using either a property of the item or a function to select keys
40+
*/
2341
toMap<K extends StringProperty<T>, U extends string = never>(
2442
selector: KeySelector<T, U> | K
2543
): Promise<Map<Coalesce<U, T[K]>, T>>
26-
/** Returns an iterator directly from the underlying generator, preserving values returned. */
44+
45+
/**
46+
* Returns an iterator directly from the underlying generator, preserving values returned.
47+
*/
2748
iterator(): AsyncIterator<T, T | void>
2849
}
2950

@@ -55,8 +76,8 @@ export function toCollection<T>(generator: () => AsyncGenerator<T, T | undefined
5576
flatten: () => toCollection<SafeUnboxIterable<T>>(() => delegateGenerator(generator(), flatten)),
5677
filter: <U extends T>(predicate: Predicate<T, U>) =>
5778
toCollection<U>(() => filterGenerator<T, U>(generator(), predicate)),
58-
map: <U>(fn: (item: T) => U) => toCollection<U>(() => mapGenerator(generator(), fn)),
59-
take: (count: number) => toCollection(() => delegateGenerator(generator(), takeFrom(count))),
79+
map: <U>(fn: (item: T) => Promise<U> | U) => toCollection<U>(() => mapGenerator(generator(), fn)),
80+
limit: (count: number) => toCollection(() => delegateGenerator(generator(), takeFrom(count))),
6081
promise: () => promise(iterable),
6182
toMap: <U extends string = never, K extends StringProperty<T> = never>(selector: KeySelector<T, U> | K) =>
6283
asyncIterableToMap(iterable, selector),
@@ -66,7 +87,7 @@ export function toCollection<T>(generator: () => AsyncGenerator<T, T | undefined
6687

6788
async function* mapGenerator<T, U, R = T>(
6889
generator: AsyncGenerator<T, R | undefined | void>,
69-
fn: (item: T | R) => U
90+
fn: (item: T | R) => Promise<U> | U
7091
): AsyncGenerator<U, U | undefined> {
7192
while (true) {
7293
const { value, done } = await generator.next()
@@ -79,9 +100,11 @@ async function* mapGenerator<T, U, R = T>(
79100
}
80101
}
81102

103+
type Predicate<T, U extends T> = (item: T) => item is U
104+
82105
async function* filterGenerator<T, U extends T, R = T>(
83106
generator: AsyncGenerator<T, R | undefined | void>,
84-
predicate: Predicate<T | R, U>
107+
predicate: Predicate<T | R, U> | ((item: T | R) => boolean)
85108
): AsyncGenerator<U, U | void> {
86109
while (true) {
87110
const { value, done } = await generator.next()
@@ -159,8 +182,6 @@ async function promise<T>(iterable: AsyncIterable<T>): Promise<T[]> {
159182
return result
160183
}
161184

162-
type Predicate<T, U extends T> = ((item: T) => item is U) | ((item: T) => boolean)
163-
164185
function addToMap<T, U extends string>(map: Map<string, T>, selector: KeySelector<T, U> | StringProperty<T>, item: T) {
165186
const key = typeof selector === 'function' ? selector(item) : item[selector]
166187
if (key) {

src/test/shared/utilities/asyncCollection.test.ts

Lines changed: 22 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -78,16 +78,16 @@ describe('AsyncCollection', function () {
7878
assert.deepStrictEqual(await filtered.promise(), expected)
7979
})
8080

81-
it('can take', async function () {
82-
const take = toCollection(gen).take(2)
83-
assert.deepStrictEqual(await take.promise(), [0, 1])
81+
it('can limit', async function () {
82+
const limited = toCollection(gen).limit(2)
83+
assert.deepStrictEqual(await limited.promise(), [0, 1])
8484
})
8585

86-
it('returns nothing if using non-positive count', async function () {
87-
const takeZero = toCollection(gen).take(0)
88-
const takeNeg1 = toCollection(gen).take(-1)
89-
assert.deepStrictEqual(await takeZero.promise(), [])
90-
assert.deepStrictEqual(await takeNeg1.promise(), [])
86+
it('returns nothing if using non-positive limit count', async function () {
87+
const limitZero = toCollection(gen).limit(0)
88+
const limitNeg1 = toCollection(gen).limit(-1)
89+
assert.deepStrictEqual(await limitZero.promise(), [])
90+
assert.deepStrictEqual(await limitNeg1.promise(), [])
9191
})
9292

9393
it('is immutable', async function () {
@@ -103,6 +103,19 @@ describe('AsyncCollection', function () {
103103
assert.deepStrictEqual(await z.promise(), [2, 2, 3, 3])
104104
})
105105

106+
it('can map with async functions', async function () {
107+
const double = async (n: number) => 2 * n
108+
109+
const mapped = toCollection(gen)
110+
.map(o => o + 1)
111+
.map(double)
112+
.map(o => o - 1)
113+
.map(double)
114+
.map(o => `${o}!`)
115+
116+
assert.deepStrictEqual(await mapped.promise(), ['2!', '6!', '10!'])
117+
})
118+
106119
it('does not iterate over the generator when applying transformations', async function () {
107120
let callCount = 0
108121
async function* count() {
@@ -117,7 +130,7 @@ describe('AsyncCollection', function () {
117130
.flatten()
118131
await new Promise(r => setImmediate(r))
119132
assert.strictEqual(callCount, 0)
120-
assert.deepStrictEqual(await x.take(6).promise(), [2, 4, 3, 9, 4, 16])
133+
assert.deepStrictEqual(await x.limit(6).promise(), [2, 4, 3, 9, 4, 16])
121134
assert.strictEqual(callCount, 6)
122135
})
123136

0 commit comments

Comments
 (0)