Skip to content

Commit d91b66f

Browse files
committed
Revert "abort without a pending next"
This reverts commit b42febb.
1 parent b42febb commit d91b66f

File tree

3 files changed

+76
-72
lines changed

3 files changed

+76
-72
lines changed

src/execution/PromiseCanceller.ts

Lines changed: 11 additions & 31 deletions
Original file line numberDiff line numberDiff line change
@@ -28,14 +28,19 @@ export class PromiseCanceller {
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?: (() => unknown) | undefined,
34+
): Promise<T> {
3235
if (this.abortSignal.aborted) {
36+
onCancel?.();
3337
// eslint-disable-next-line @typescript-eslint/prefer-promise-reject-errors
3438
return Promise.reject(this.abortSignal.reason);
3539
}
3640

3741
const { promise, resolve, reject } = promiseWithResolvers<T>();
3842
const abort = () => {
43+
onCancel?.();
3944
reject(this.abortSignal.reason);
4045
};
4146
this._aborts.add(abort);
@@ -57,45 +62,20 @@ export class PromiseCanceller {
5762
const iterator = iterable[Symbol.asyncIterator]();
5863

5964
if (iterator.return) {
60-
const _next = iterator.next.bind(iterator);
6165
const _return = iterator.return.bind(iterator);
62-
63-
const abort = () => {
66+
const _returnIgnoringErrors = async (): Promise<IteratorResult<T>> => {
6467
_return().catch(() => {
6568
/* c8 ignore next */
6669
// ignore
6770
});
71+
return Promise.resolve({ value: undefined, done: true });
6872
};
6973

70-
if (this.abortSignal.aborted) {
71-
abort();
72-
// eslint-disable-next-line @typescript-eslint/prefer-promise-reject-errors
73-
const onMethod = () => Promise.reject(this.abortSignal.reason);
74-
return {
75-
[Symbol.asyncIterator]: () => ({
76-
next: onMethod,
77-
return: onMethod,
78-
}),
79-
};
80-
}
81-
82-
this._aborts.add(abort);
83-
const on = (method: () => Promise<IteratorResult<T>>) => async () => {
84-
try {
85-
const iteration = await this.cancellablePromise(method());
86-
if (iteration.done) {
87-
this._aborts.delete(abort);
88-
}
89-
return iteration;
90-
} catch (error) {
91-
this._aborts.delete(abort);
92-
throw error;
93-
}
94-
};
9574
return {
9675
[Symbol.asyncIterator]: () => ({
97-
next: on(_next),
98-
return: on(_return),
76+
next: () =>
77+
this.cancellablePromise(iterator.next(), _returnIgnoringErrors),
78+
return: () => this.cancellablePromise(_return()),
9979
}),
10080
};
10181
}

src/execution/__tests__/PromiseCanceller-test.ts

Lines changed: 63 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -70,6 +70,62 @@ describe('PromiseCanceller', () => {
7070

7171
await expectPromise(withCancellation).toRejectWith('Cancelled!');
7272
});
73+
74+
it('works to trigger onCancel when cancelling a hanging promise', async () => {
75+
const abortController = new AbortController();
76+
const abortSignal = abortController.signal;
77+
78+
const promiseCanceller = new PromiseCanceller(abortSignal);
79+
80+
const promise = new Promise(() => {
81+
/* never resolves */
82+
});
83+
84+
let onCancelCalled = false;
85+
const onCancel = () => {
86+
onCancelCalled = true;
87+
};
88+
89+
const withCancellation = promiseCanceller.cancellablePromise(
90+
promise,
91+
onCancel,
92+
);
93+
94+
expect(onCancelCalled).to.equal(false);
95+
96+
abortController.abort(new Error('Cancelled!'));
97+
98+
expect(onCancelCalled).to.equal(true);
99+
100+
await expectPromise(withCancellation).toRejectWith('Cancelled!');
101+
});
102+
103+
it('works to trigger onCancel when cancelling a hanging promise created after abort signal triggered', async () => {
104+
const abortController = new AbortController();
105+
const abortSignal = abortController.signal;
106+
107+
abortController.abort(new Error('Cancelled!'));
108+
109+
const promiseCanceller = new PromiseCanceller(abortSignal);
110+
111+
const promise = new Promise(() => {
112+
/* never resolves */
113+
});
114+
115+
let onCancelCalled = false;
116+
const onCancel = () => {
117+
onCancelCalled = true;
118+
};
119+
120+
const withCancellation = promiseCanceller.cancellablePromise(
121+
promise,
122+
onCancel,
123+
);
124+
125+
expect(onCancelCalled).to.equal(true);
126+
127+
await expectPromise(withCancellation).toRejectWith('Cancelled!');
128+
});
73129
});
74130

75131
describe('cancellableAsyncIterable', () => {
@@ -141,11 +197,13 @@ describe('PromiseCanceller', () => {
141197

142198
abortController.abort(new Error('Cancelled!'));
143199

144-
expect(returned).to.equal(true);
200+
expect(returned).to.equal(false);
145201

146202
const nextPromise =
147203
cancellableAsyncIterable[Symbol.asyncIterator]().next();
148204

205+
expect(returned).to.equal(true);
206+
149207
await expectPromise(nextPromise).toRejectWith('Cancelled!');
150208
});
151209

@@ -160,8 +218,7 @@ describe('PromiseCanceller', () => {
160218
let returned = false;
161219
const asyncIterable = {
162220
[Symbol.asyncIterator]: () => ({
163-
/* c8 ignore next */
164-
next: () => Promise.resolve({ value: undefined, done: true }),
221+
next: () => Promise.resolve({ value: 1, done: false }),
165222
return: () => {
166223
returned = true;
167224
return Promise.resolve({ value: undefined, done: true });
@@ -172,11 +229,13 @@ describe('PromiseCanceller', () => {
172229
const cancellableAsyncIterable =
173230
promiseCanceller.cancellableIterable(asyncIterable);
174231

175-
expect(returned).to.equal(true);
232+
expect(returned).to.equal(false);
176233

177234
const nextPromise =
178235
cancellableAsyncIterable[Symbol.asyncIterator]().next();
179236

237+
expect(returned).to.equal(true);
238+
180239
await expectPromise(nextPromise).toRejectWith('Cancelled!');
181240
});
182241
});

src/execution/__tests__/abort-signal-test.ts

Lines changed: 2 additions & 37 deletions
Original file line numberDiff line numberDiff line change
@@ -795,7 +795,7 @@ describe('Execute: Cancellation', () => {
795795
});
796796
});
797797

798-
it('should stop the execution when aborted prior to return of the subscription resolver', async () => {
798+
it('should stop the execution when aborted during subscription', async () => {
799799
const abortController = new AbortController();
800800
const document = parse(`
801801
subscription {
@@ -830,42 +830,6 @@ describe('Execute: Cancellation', () => {
830830
});
831831
});
832832

833-
it('should successfully wrap the subscription', async () => {
834-
const abortController = new AbortController();
835-
const document = parse(`
836-
subscription {
837-
foo
838-
}
839-
`);
840-
841-
const subscription = subscribe({
842-
document,
843-
schema,
844-
abortSignal: abortController.signal,
845-
rootValue: {
846-
async *foo() {
847-
yield await Promise.resolve({ foo: 'foo' });
848-
},
849-
},
850-
});
851-
852-
assert(isAsyncIterable(subscription));
853-
854-
expectJSON(await subscription.next()).toDeepEqual({
855-
value: {
856-
data: {
857-
foo: 'foo',
858-
},
859-
},
860-
done: false,
861-
});
862-
863-
expectJSON(await subscription.next()).toDeepEqual({
864-
value: undefined,
865-
done: true,
866-
});
867-
});
868-
869833
it('should stop the execution when aborted during subscription', async () => {
870834
const abortController = new AbortController();
871835
const document = parse(`
@@ -880,6 +844,7 @@ describe('Execute: Cancellation', () => {
880844
abortSignal: abortController.signal,
881845
rootValue: {
882846
async *foo() {
847+
yield await Promise.resolve({ foo: 'foo' });
883848
yield await Promise.resolve({ foo: 'foo' }); /* c8 ignore start */
884849
} /* c8 ignore stop */,
885850
},

0 commit comments

Comments
 (0)