Skip to content

Commit 5a99914

Browse files
committed
add abort signal support to our async iterables
1 parent 3036d41 commit 5a99914

File tree

2 files changed

+33
-3
lines changed

2 files changed

+33
-3
lines changed

src/execution/AbortSignalListener.ts

Lines changed: 29 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -28,8 +28,14 @@ export class AbortSignalListener {
2828
this.abortSignal.removeEventListener('abort', this.abort);
2929
}
3030

31-
cancellablePromise<T>(originalPromise: Promise<T>): Promise<T> {
31+
cancellablePromise<T>(
32+
originalPromise: Promise<T>,
33+
onCancel?: (() => Promise<unknown>) | undefined,
34+
): Promise<T> {
3235
if (this.abortSignal.aborted) {
36+
onCancel?.().catch(() => {
37+
// ignore
38+
});
3339
// eslint-disable-next-line @typescript-eslint/prefer-promise-reject-errors
3440
return Promise.reject(this.abortSignal.reason);
3541
}
@@ -50,4 +56,26 @@ export class AbortSignalListener {
5056

5157
return promise;
5258
}
59+
60+
cancellableIterable<T>(iterable: AsyncIterable<T>): AsyncIterable<T> {
61+
const iterator = iterable[Symbol.asyncIterator]();
62+
63+
const earlyReturn =
64+
typeof iterator.return === 'function'
65+
? iterator.return.bind(iterator)
66+
: undefined;
67+
68+
const cancellableAsyncIterator: AsyncIterator<T> = {
69+
next: () => this.cancellablePromise(iterator.next(), earlyReturn),
70+
};
71+
72+
if (earlyReturn) {
73+
cancellableAsyncIterator.return = () =>
74+
this.cancellablePromise(earlyReturn());
75+
}
76+
77+
return {
78+
[Symbol.asyncIterator]: () => cancellableAsyncIterator,
79+
};
80+
}
5381
}

src/execution/execute.ts

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1387,7 +1387,9 @@ function completeListValue(
13871387
const itemType = returnType.ofType;
13881388

13891389
if (isAsyncIterable(result)) {
1390-
const asyncIterator = result[Symbol.asyncIterator]();
1390+
const maybeCancellableIterable =
1391+
exeContext.abortSignalListener?.cancellableIterable(result) ?? result;
1392+
const asyncIterator = maybeCancellableIterable[Symbol.asyncIterator]();
13911393

13921394
return completeAsyncIteratorValue(
13931395
exeContext,
@@ -2229,7 +2231,7 @@ function executeSubscription(
22292231
// TODO: add test case
22302232
/* c8 ignore next */
22312233
abortSignalListener?.disconnect();
2232-
return resolved;
2234+
return abortSignalListener?.cancellableIterable(resolved) ?? resolved;
22332235
},
22342236
(error: unknown) => {
22352237
abortSignalListener?.disconnect();

0 commit comments

Comments
 (0)