Skip to content

Commit 0883092

Browse files
committed
fix(asynciterable): use more yield
1 parent 7221be2 commit 0883092

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

110 files changed

+788
-786
lines changed

spec/asynciterable-operators/finalize-spec.ts

+2-2
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
import { hasNext, hasErr, noNext } from '../asynciterablehelpers.js';
22
import { range, throwError } from 'ix/asynciterable/index.js';
3-
import { flatMap, finalize, tap } from 'ix/asynciterable/operators/index.js';
3+
import { finalize, tap, concatMap } from 'ix/asynciterable/operators/index.js';
44

55
test('AsyncIterable#finalize defers behavior', async () => {
66
let done = false;
@@ -79,7 +79,7 @@ test('AsyncIterable#finalize calls with downstream error from flattening', async
7979
finalize(async () => {
8080
done = true;
8181
}),
82-
flatMap(async (x) => {
82+
concatMap(async (x) => {
8383
// srcValues.push(x);
8484
if (x === 1) {
8585
return throwError(err);

src/add/asynciterable-operators/mergeall.ts

+1-1
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@ export function mergeAllProto<T>(
88
this: AsyncIterableX<AsyncIterable<T>>,
99
concurrent = Infinity
1010
): AsyncIterableX<T> {
11-
return mergeAll(concurrent)(this);
11+
return mergeAll<T>(concurrent)(this);
1212
}
1313

1414
AsyncIterableX.prototype.mergeAll = mergeAllProto;

src/asynciterable/_extremaby.ts

+21-19
Original file line numberDiff line numberDiff line change
@@ -9,29 +9,31 @@ export async function extremaBy<TSource, TKey>(
99
): Promise<TSource[]> {
1010
throwIfAborted(signal);
1111

12-
let result = [];
13-
const it = wrapWithAbort(source, signal)[Symbol.asyncIterator]();
14-
const { value, done } = await it.next();
15-
if (done) {
16-
throw new Error('Sequence contains no elements');
17-
}
18-
19-
let resKey = await selector(value, signal);
20-
result.push(value);
12+
let hasValue = false;
13+
let key: TKey | undefined;
14+
let result: TSource[] = [];
2115

22-
let next: IteratorResult<TSource>;
23-
while (!(next = await it.next()).done) {
24-
const current = next.value;
25-
const key = await selector(current, signal);
26-
const cmp = await comparer(key, resKey, signal);
16+
for await (const item of wrapWithAbort(source, signal)) {
17+
if (!hasValue) {
18+
key = await selector(item, signal);
19+
result.push(item);
20+
hasValue = true;
21+
} else {
22+
const currentKey = await selector(item, signal);
23+
const cmp = await comparer(currentKey, key as TKey, signal);
2724

28-
if (cmp === 0) {
29-
result.push(current);
30-
} else if (cmp > 0) {
31-
result = [current];
32-
resKey = key;
25+
if (cmp === 0) {
26+
result.push(item);
27+
} else if (cmp > 0) {
28+
result = [item];
29+
key = currentKey;
30+
}
3331
}
3432
}
3533

34+
if (!hasValue) {
35+
throw new Error('Sequence contains no elements');
36+
}
37+
3638
return result;
3739
}

src/asynciterable/asynciterablex.ts

+1-2
Original file line numberDiff line numberDiff line change
@@ -290,8 +290,7 @@ export class FromPromiseIterable<TSource, TResult = TSource> extends AsyncIterab
290290
}
291291

292292
async *[Symbol.asyncIterator]() {
293-
const item = await this._source;
294-
yield await this._selector(item, 0);
293+
yield await this._selector(await this._source, 0);
295294
}
296295
}
297296

src/asynciterable/average.ts

+3
Original file line numberDiff line numberDiff line change
@@ -42,9 +42,12 @@ export async function average(
4242
['signal']: signal,
4343
['thisArg']: thisArg,
4444
} = options || {};
45+
4546
throwIfAborted(signal);
47+
4648
let sum = 0;
4749
let count = 0;
50+
4851
for await (const item of wrapWithAbort(source, signal)) {
4952
sum += await selector.call(thisArg, item, signal);
5053
count++;

src/asynciterable/catcherror.ts

+7-23
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,4 @@
11
import { AsyncIterableX } from './asynciterablex.js';
2-
import { returnAsyncIterator } from '../util/returniterator.js';
32
import { wrapWithAbort } from './operators/withabort.js';
43
import { throwIfAborted } from '../aborterror.js';
54

@@ -19,29 +18,14 @@ export class CatchAllAsyncIterable<TSource> extends AsyncIterableX<TSource> {
1918
let hasError = false;
2019

2120
for (const source of this._source) {
22-
const it = wrapWithAbort(source, signal)[Symbol.asyncIterator]();
23-
2421
error = null;
2522
hasError = false;
2623

27-
while (1) {
28-
let c = <TSource>{};
29-
30-
try {
31-
const { done, value } = await it.next();
32-
if (done) {
33-
await returnAsyncIterator(it);
34-
break;
35-
}
36-
c = value;
37-
} catch (e) {
38-
error = e;
39-
hasError = true;
40-
await returnAsyncIterator(it);
41-
break;
42-
}
43-
44-
yield c;
24+
try {
25+
yield* wrapWithAbort(source, signal);
26+
} catch (e) {
27+
error = e;
28+
hasError = true;
4529
}
4630

4731
if (!hasError) {
@@ -64,7 +48,7 @@ export class CatchAllAsyncIterable<TSource> extends AsyncIterableX<TSource> {
6448
* sequences until a source sequence terminates successfully.
6549
*/
6650
export function catchAll<T>(source: Iterable<AsyncIterable<T>>): AsyncIterableX<T> {
67-
return new CatchAllAsyncIterable<T>(source);
51+
return new CatchAllAsyncIterable(source);
6852
}
6953

7054
/**
@@ -76,5 +60,5 @@ export function catchAll<T>(source: Iterable<AsyncIterable<T>>): AsyncIterableX<
7660
* sequences until a source sequence terminates successfully.
7761
*/
7862
export function catchError<T>(...args: AsyncIterable<T>[]): AsyncIterableX<T> {
79-
return new CatchAllAsyncIterable<T>(args);
63+
return new CatchAllAsyncIterable(args);
8064
}

src/asynciterable/combinelatest.ts

+27-23
Original file line numberDiff line numberDiff line change
@@ -3,9 +3,10 @@ import { identity } from '../util/identity.js';
33
import { wrapWithAbort } from './operators/withabort.js';
44
import { throwIfAborted } from '../aborterror.js';
55
import { safeRace } from '../util/safeRace.js';
6+
import { returnAsyncIterators } from '../util/returniterator.js';
67

78
// eslint-disable-next-line @typescript-eslint/no-empty-function
8-
const NEVER_PROMISE = new Promise(() => {});
9+
const NEVER_PROMISE = new Promise<never>(() => {});
910

1011
type MergeResult<T> = { value: T; index: number };
1112

@@ -28,39 +29,42 @@ export class CombineLatestAsyncIterable<TSource> extends AsyncIterableX<TSource[
2829
const length = this._sources.length;
2930
const iterators = new Array<AsyncIterator<TSource>>(length);
3031
const nexts = new Array<Promise<MergeResult<IteratorResult<TSource>>>>(length);
31-
let hasValueAll = false;
32-
const values = new Array<TSource>(length);
33-
const hasValues = new Array<boolean>(length);
34-
let active = length;
3532

36-
hasValues.fill(false);
33+
let active = length;
34+
let allValuesAvailable = false;
35+
const values = new Array<TSource>(length);
36+
const hasValues = new Array<boolean>(length).fill(false);
3737

3838
for (let i = 0; i < length; i++) {
3939
const iterator = wrapWithAbort(this._sources[i], signal)[Symbol.asyncIterator]();
4040
iterators[i] = iterator;
4141
nexts[i] = wrapPromiseWithIndex(iterator.next(), i);
4242
}
4343

44-
while (active > 0) {
45-
const next = safeRace(nexts);
46-
const {
47-
value: { value: value$, done: done$ },
48-
index,
49-
} = await next;
50-
if (done$) {
51-
nexts[index] = <Promise<MergeResult<IteratorResult<TSource>>>>NEVER_PROMISE;
52-
active--;
53-
} else {
54-
values[index] = value$;
55-
hasValues[index] = true;
44+
try {
45+
while (active > 0) {
46+
const {
47+
value: { value, done },
48+
index,
49+
} = await safeRace(nexts);
50+
51+
if (done) {
52+
nexts[index] = NEVER_PROMISE;
53+
active--;
54+
} else {
55+
values[index] = value;
56+
hasValues[index] = true;
57+
allValuesAvailable = allValuesAvailable || hasValues.every(identity);
5658

57-
const iterator$ = iterators[index];
58-
nexts[index] = wrapPromiseWithIndex(iterator$.next(), index);
59+
nexts[index] = wrapPromiseWithIndex(iterators[index].next(), index);
5960

60-
if (hasValueAll || (hasValueAll = hasValues.every(identity))) {
61-
yield values;
61+
if (allValuesAvailable) {
62+
yield values;
63+
}
6264
}
6365
}
66+
} finally {
67+
await returnAsyncIterators(iterators);
6468
}
6569
}
6670
}
@@ -176,5 +180,5 @@ export function combineLatest<T, T2, T3, T4, T5, T6>(
176180
*/
177181
export function combineLatest<T>(...sources: AsyncIterable<T>[]): AsyncIterableX<T[]>;
178182
export function combineLatest<T>(...sources: any[]): AsyncIterableX<T[]> {
179-
return new CombineLatestAsyncIterable<T>(sources);
183+
return new CombineLatestAsyncIterable(sources);
180184
}

src/asynciterable/concat.ts

+4-5
Original file line numberDiff line numberDiff line change
@@ -13,18 +13,17 @@ export class ConcatAsyncIterable<TSource> extends AsyncIterableX<TSource> {
1313

1414
async *[Symbol.asyncIterator](signal?: AbortSignal) {
1515
throwIfAborted(signal);
16+
1617
for (const outer of this._source) {
17-
for await (const item of wrapWithAbort(outer, signal)) {
18-
yield item;
19-
}
18+
yield* wrapWithAbort(outer, signal);
2019
}
2120
}
2221
}
2322

2423
export function _concatAll<TSource>(
2524
source: Iterable<AsyncIterable<TSource>>
2625
): AsyncIterableX<TSource> {
27-
return new ConcatAsyncIterable<TSource>(source);
26+
return new ConcatAsyncIterable(source);
2827
}
2928

3029
/**
@@ -136,5 +135,5 @@ export function concat<T, T2, T3, T4, T5, T6>(
136135
* @returns {AsyncIterableX<T>} An async-iterable sequence that contains the elements of each given sequence, in sequential order.
137136
*/
138137
export function concat<T>(...args: AsyncIterable<T>[]): AsyncIterableX<T> {
139-
return new ConcatAsyncIterable<T>(args);
138+
return new ConcatAsyncIterable(args);
140139
}

src/asynciterable/count.ts

+2-1
Original file line numberDiff line numberDiff line change
@@ -18,9 +18,10 @@ export async function count<T>(
1818
): Promise<number> {
1919
const { ['signal']: signal, ['thisArg']: thisArg, ['predicate']: predicate = async () => true } =
2020
options || {};
21+
2122
throwIfAborted(signal);
22-
let i = 0;
2323

24+
let i = 0;
2425
for await (const item of wrapWithAbort(source, signal)) {
2526
if (await predicate.call(thisArg, item, i, signal)) {
2627
i++;

src/asynciterable/create.ts

+5-4
Original file line numberDiff line numberDiff line change
@@ -11,11 +11,12 @@ class AnonymousAsyncIterable<T> extends AsyncIterableX<T> {
1111

1212
async *[Symbol.asyncIterator](signal?: AbortSignal) {
1313
throwIfAborted(signal);
14+
1415
const it = await this._fn(signal);
15-
let next: IteratorResult<T> | undefined;
16-
while (!(next = await it.next()).done) {
17-
yield next.value;
18-
}
16+
17+
yield* {
18+
[Symbol.asyncIterator]: () => it,
19+
};
1920
}
2021
}
2122

src/asynciterable/defer.ts

+3-5
Original file line numberDiff line numberDiff line change
@@ -14,10 +14,8 @@ class DeferAsyncIterable<TSource> extends AsyncIterableX<TSource> {
1414

1515
async *[Symbol.asyncIterator](signal?: AbortSignal) {
1616
throwIfAborted(signal);
17-
const items = await this._fn(signal);
18-
for await (const item of wrapWithAbort(items, signal)) {
19-
yield item;
20-
}
17+
18+
yield* wrapWithAbort(await this._fn(signal), signal);
2119
}
2220
}
2321

@@ -32,5 +30,5 @@ class DeferAsyncIterable<TSource> extends AsyncIterableX<TSource> {
3230
export function defer<TSource>(
3331
factory: (signal?: AbortSignal) => AsyncIterable<TSource> | Promise<AsyncIterable<TSource>>
3432
): AsyncIterableX<TSource> {
35-
return new DeferAsyncIterable<TSource>(factory);
33+
return new DeferAsyncIterable(factory);
3634
}

src/asynciterable/elementat.ts

+2
Original file line numberDiff line numberDiff line change
@@ -17,12 +17,14 @@ export async function elementAt<T>(
1717
signal?: AbortSignal
1818
): Promise<T | undefined> {
1919
throwIfAborted(signal);
20+
2021
let i = index;
2122
for await (const item of wrapWithAbort(source, signal)) {
2223
if (i === 0) {
2324
return item;
2425
}
2526
i--;
2627
}
28+
2729
return undefined;
2830
}

src/asynciterable/every.ts

+3
Original file line numberDiff line numberDiff line change
@@ -16,12 +16,15 @@ export async function every<T>(
1616
options: FindOptions<T>
1717
): Promise<boolean> {
1818
const { ['signal']: signal, ['thisArg']: thisArg, ['predicate']: predicate } = options;
19+
1920
throwIfAborted(signal);
21+
2022
let i = 0;
2123
for await (const item of wrapWithAbort(source, signal)) {
2224
if (!(await predicate.call(thisArg, item, i++, signal))) {
2325
return false;
2426
}
2527
}
28+
2629
return true;
2730
}

src/asynciterable/find.ts

+3-1
Original file line numberDiff line numberDiff line change
@@ -15,13 +15,15 @@ export async function find<T>(
1515
options: FindOptions<T>
1616
): Promise<T | undefined> {
1717
const { ['signal']: signal, ['thisArg']: thisArg, ['predicate']: predicate } = options;
18+
1819
throwIfAborted(signal);
19-
let i = 0;
2020

21+
let i = 0;
2122
for await (const item of wrapWithAbort(source, signal)) {
2223
if (await predicate.call(thisArg, item, i++, signal)) {
2324
return item;
2425
}
2526
}
27+
2628
return undefined;
2729
}

src/asynciterable/findindex.ts

+3-1
Original file line numberDiff line numberDiff line change
@@ -16,13 +16,15 @@ export async function findIndex<T>(
1616
options: FindOptions<T>
1717
): Promise<number> {
1818
const { ['signal']: signal, ['thisArg']: thisArg, ['predicate']: predicate } = options;
19+
1920
throwIfAborted(signal);
20-
let i = 0;
2121

22+
let i = 0;
2223
for await (const item of wrapWithAbort(source, signal)) {
2324
if (await predicate.call(thisArg, item, i++, signal)) {
2425
return i;
2526
}
2627
}
28+
2729
return -1;
2830
}

src/asynciterable/first.ts

+3-1
Original file line numberDiff line numberDiff line change
@@ -16,10 +16,12 @@ export async function first<T>(
1616
): Promise<T | undefined> {
1717
const { ['signal']: signal, ['thisArg']: thisArg, ['predicate']: predicate = async () => true } =
1818
options || {};
19+
1920
throwIfAborted(signal);
21+
2022
let i = 0;
2123
for await (const item of wrapWithAbort(source, signal)) {
22-
if (await predicate!.call(thisArg, item, i++, signal)) {
24+
if (await predicate.call(thisArg, item, i++, signal)) {
2325
return item;
2426
}
2527
}

0 commit comments

Comments
 (0)