Skip to content

Commit 9e2f532

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

File tree

2 files changed

+36
-3
lines changed

2 files changed

+36
-3
lines changed

src/execution/AbortSignalListener.ts

Lines changed: 32 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -28,7 +28,10 @@ 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) {
3336
// eslint-disable-next-line @typescript-eslint/prefer-promise-reject-errors
3437
return Promise.reject(this.abortSignal.reason);
@@ -40,14 +43,42 @@ export class AbortSignalListener {
4043
originalPromise.then(
4144
(resolved) => {
4245
this._aborts.delete(abort);
46+
onCancel?.().catch(() => {
47+
// ignore
48+
});
4349
resolve(resolved);
4450
},
4551
(error: unknown) => {
4652
this._aborts.delete(abort);
53+
onCancel?.().catch(() => {
54+
// ignore
55+
});
4756
reject(error);
4857
},
4958
);
5059

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

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)