Skip to content

Commit e004fa2

Browse files
committed
replace mapAsyncIterator with repeater-based mapAsyncIterable
1 parent b089cca commit e004fa2

File tree

2 files changed

+64
-50
lines changed

2 files changed

+64
-50
lines changed

src/execution/__tests__/mapAsyncIterable-test.ts

Lines changed: 14 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -109,7 +109,7 @@ describe('mapAsyncIterable', () => {
109109

110110
// Early return
111111
expect(await doubles.return('')).to.deep.equal({
112-
value: 'The End',
112+
value: '',
113113
done: true,
114114
});
115115

@@ -148,20 +148,24 @@ describe('mapAsyncIterable', () => {
148148

149149
// Early return
150150
expect(await doubles.return(0)).to.deep.equal({
151-
value: undefined,
151+
value: 0,
152152
done: true,
153153
});
154154
});
155155

156156
it('passes through early return from async values', async () => {
157+
let didVisitFinally = false;
158+
157159
async function* source() {
158160
try {
159161
yield 'a';
160162
/* c8 ignore next 3 */
161163
yield 'b';
162164
yield 'c'; // Shouldn't be reached.
163165
} finally {
166+
didVisitFinally = true;
164167
yield 'Done';
168+
/* c8 ignore next 2 */
165169
yield 'Last';
166170
}
167171
}
@@ -173,19 +177,21 @@ describe('mapAsyncIterable', () => {
173177

174178
// Early return
175179
expect(await doubles.return()).to.deep.equal({
176-
value: 'DoneDone',
177-
done: false,
180+
value: undefined,
181+
done: true,
178182
});
179183

180-
// Subsequent next calls may yield from finally block
184+
// Subsequent next calls
181185
expect(await doubles.next()).to.deep.equal({
182-
value: 'LastLast',
183-
done: false,
186+
value: undefined,
187+
done: true,
184188
});
185189
expect(await doubles.next()).to.deep.equal({
186190
value: undefined,
187191
done: true,
188192
});
193+
194+
expect(didVisitFinally).to.equal(true);
189195
});
190196

191197
it('allows throwing errors through async iterable', async () => {
@@ -277,6 +283,7 @@ describe('mapAsyncIterable', () => {
277283
} finally {
278284
didVisitFinally = true;
279285
yield 1000;
286+
/* c8 ignore next 1 */
280287
}
281288
}
282289

src/execution/mapAsyncIterable.ts

Lines changed: 50 additions & 43 deletions
Original file line numberDiff line numberDiff line change
@@ -1,57 +1,64 @@
1+
import { isPromise } from '../jsutils/isPromise';
12
import type { PromiseOrValue } from '../jsutils/PromiseOrValue';
3+
import { Repeater } from '../jsutils/Repeater';
24

35
/**
4-
* Given an AsyncIterable and a callback function, return an AsyncIterator
6+
* Given an AsyncIterable and a callback function, return an AsyncGenerator
57
* which produces values mapped via calling the callback function.
68
*/
79
export function mapAsyncIterable<T, U, R = undefined>(
810
iterable: AsyncGenerator<T, R, void> | AsyncIterable<T>,
9-
callback: (value: T) => PromiseOrValue<U>,
11+
fn: (value: T) => PromiseOrValue<U>,
1012
): AsyncGenerator<U, R, void> {
11-
const iterator = iterable[Symbol.asyncIterator]();
13+
return new Repeater<U, R, void>(async (push, stop, stopped) => {
14+
const iter: AsyncIterator<T, R, void> = iterable[Symbol.asyncIterator]();
15+
let finalIteration: PromiseOrValue<IteratorResult<T, R>> | undefined;
16+
// The stopped promise never rejects.
17+
// eslint-disable-next-line @typescript-eslint/no-floating-promises
18+
stopped.then(() => {
19+
if (typeof iter.return === 'function') {
20+
finalIteration = iter.return();
21+
}
22+
});
1223

13-
async function mapResult(
14-
result: IteratorResult<T, R>,
15-
): Promise<IteratorResult<U, R>> {
16-
if (result.done) {
17-
return result;
18-
}
24+
let next = iter.next();
25+
// eslint-disable-next-line no-unmodified-loop-condition
26+
while (!finalIteration) {
27+
// eslint-disable-next-line no-await-in-loop
28+
const { done, value } = await next;
29+
30+
if (done) {
31+
stop();
32+
break;
33+
}
34+
35+
let mapped: U;
36+
try {
37+
// eslint-disable-next-line no-await-in-loop
38+
mapped = await fn(value);
39+
} catch (err) {
40+
stop(err);
41+
break;
42+
}
1943

20-
try {
21-
return { value: await callback(result.value), done: false };
22-
} catch (error) {
23-
/* c8 ignore start */
24-
// FIXME: add test case
25-
if (typeof iterator.return === 'function') {
26-
try {
27-
await iterator.return();
28-
} catch (_e) {
29-
/* ignore error */
44+
try {
45+
// eslint-disable-next-line no-await-in-loop
46+
await push(mapped);
47+
} catch (err) {
48+
if (typeof iter.throw === 'function') {
49+
next = iter.throw(err);
50+
continue;
3051
}
52+
throw err;
3153
}
32-
throw error;
33-
/* c8 ignore stop */
54+
55+
next = iter.next();
3456
}
35-
}
36-
37-
return {
38-
async next() {
39-
return mapResult(await iterator.next());
40-
},
41-
async return(): Promise<IteratorResult<U, R>> {
42-
// If iterator.return() does not exist, then type R must be undefined.
43-
return typeof iterator.return === 'function'
44-
? mapResult(await iterator.return())
45-
: { value: undefined as any, done: true };
46-
},
47-
async throw(error?: unknown) {
48-
if (typeof iterator.throw === 'function') {
49-
return mapResult(await iterator.throw(error));
50-
}
51-
throw error;
52-
},
53-
[Symbol.asyncIterator]() {
54-
return this;
55-
},
56-
};
57+
58+
if (isPromise(finalIteration)) {
59+
await finalIteration;
60+
}
61+
62+
return undefined as unknown as R; // void :();
63+
});
5764
}

0 commit comments

Comments
 (0)