Skip to content

Commit 9f9b330

Browse files
committed
remove unnecessary Executor property (#4547)
onExternalAbort can be kept local the standardization/refactoring handles artificial edge case where execution finishes synchronously, but info.abortSignal is retained and then accessed artificially, requiring slight modification of existing test
1 parent 53ab326 commit 9f9b330

File tree

2 files changed

+35
-26
lines changed

2 files changed

+35
-26
lines changed

src/execution/Executor.ts

Lines changed: 25 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -198,33 +198,36 @@ export interface FormattedExecutionResult<
198198
/** @internal */
199199
export class Executor {
200200
validatedExecutionArgs: ValidatedExecutionArgs;
201-
onExternalAbort: (() => void) | undefined;
202201
finished: boolean;
203-
abortControllers: Set<AbortController>;
202+
resolverAbortControllers: Set<AbortController>;
204203
collectedErrors: CollectedErrors;
205204

206205
constructor(validatedExecutionArgs: ValidatedExecutionArgs) {
207206
this.validatedExecutionArgs = validatedExecutionArgs;
208-
this.onExternalAbort = undefined;
209207
this.finished = false;
210-
this.abortControllers = new Set();
208+
this.resolverAbortControllers = new Set();
211209
this.collectedErrors = new CollectedErrors();
212210
}
213211

214212
executeQueryOrMutationOrSubscriptionEvent(): PromiseOrValue<ExecutionResult> {
215213
const validatedExecutionArgs = this.validatedExecutionArgs;
216214
const externalAbortSignal = validatedExecutionArgs.externalAbortSignal;
215+
let removeAbortListener: (() => void) | undefined;
217216
if (externalAbortSignal) {
218217
if (externalAbortSignal.aborted) {
219218
throw new Error(externalAbortSignal.reason);
220219
}
221-
const onExternalAbort = () => {
222-
this.cancel(externalAbortSignal.reason);
223-
};
220+
const onExternalAbort = () => this.cancel(externalAbortSignal.reason);
221+
removeAbortListener = () =>
222+
externalAbortSignal.removeEventListener('abort', onExternalAbort);
224223
externalAbortSignal.addEventListener('abort', onExternalAbort);
225-
this.onExternalAbort = onExternalAbort;
226224
}
227225

226+
const onFinish = () => {
227+
removeAbortListener?.();
228+
this.finish();
229+
};
230+
228231
try {
229232
const {
230233
schema,
@@ -264,11 +267,11 @@ export class Executor {
264267
if (isPromise(result)) {
265268
const promise = result.then(
266269
(data) => {
267-
this.finish();
270+
onFinish();
268271
return this.buildResponse(data);
269272
},
270273
(error: unknown) => {
271-
this.finish();
274+
onFinish();
272275
this.collectedErrors.add(error as GraphQLError, undefined);
273276
return this.buildResponse(null);
274277
},
@@ -277,9 +280,11 @@ export class Executor {
277280
? cancellablePromise(promise, externalAbortSignal)
278281
: promise;
279282
}
283+
onFinish();
280284
return this.buildResponse(result);
281285
} catch (error) {
282286
this.collectedErrors.add(error as GraphQLError, undefined);
287+
onFinish();
283288
return this.buildResponse(null);
284289
}
285290
}
@@ -291,24 +296,20 @@ export class Executor {
291296
}
292297

293298
finish(reason?: unknown): void {
294-
if (this.finished) {
295-
return;
299+
if (!this.finished) {
300+
this.finished = true;
301+
this.triggerResolverAbortSignals(reason);
296302
}
297-
this.finished = true;
298-
const { abortControllers, onExternalAbort } = this;
303+
}
304+
305+
triggerResolverAbortSignals(reason?: unknown): void {
306+
const { resolverAbortControllers } = this;
299307
const finishReason =
300308
reason ?? new Error('Execution has already completed.');
301-
for (const abortController of abortControllers) {
309+
for (const abortController of resolverAbortControllers) {
302310
abortController.abort(finishReason);
303311
}
304-
if (onExternalAbort) {
305-
this.validatedExecutionArgs.externalAbortSignal?.removeEventListener(
306-
'abort',
307-
onExternalAbort,
308-
);
309-
}
310312
}
311-
312313
/**
313314
* Given a completed execution context and data, build the `{ errors, data }`
314315
* response defined by the "Response" section of the GraphQL specification.
@@ -480,11 +481,11 @@ export class Executor {
480481
throw new Error('Execution has already completed.');
481482
}
482483
const abortController = new AbortController();
483-
this.abortControllers.add(abortController);
484+
this.resolverAbortControllers.add(abortController);
484485
return {
485486
abortSignal: abortController.signal,
486487
unregister: () => {
487-
this.abortControllers.delete(abortController);
488+
this.resolverAbortControllers.delete(abortController);
488489
},
489490
};
490491
},

src/execution/__tests__/executor-test.ts

Lines changed: 10 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@ import { expectJSON } from '../../__testUtils__/expectJSON.js';
55
import { resolveOnNextTick } from '../../__testUtils__/resolveOnNextTick.js';
66

77
import { inspect } from '../../jsutils/inspect.js';
8+
import { promiseWithResolvers } from '../../jsutils/promiseWithResolvers.js';
89

910
import { Kind } from '../../language/kinds.js';
1011
import { parse } from '../../language/parser.js';
@@ -188,7 +189,9 @@ describe('Execute: Handles basic execution tasks', () => {
188189
});
189190
});
190191

191-
it('provides info about current execution state', () => {
192+
it('provides info about current execution state', async () => {
193+
// eslint-disable-next-line @typescript-eslint/no-invalid-void-type
194+
const { promise, resolve } = promiseWithResolvers<void>();
192195
let resolvedInfo: GraphQLResolveInfo | undefined;
193196
const testType = new GraphQLObjectType({
194197
name: 'Test',
@@ -197,6 +200,7 @@ describe('Execute: Handles basic execution tasks', () => {
197200
type: GraphQLString,
198201
resolve(_val, _args, _ctx, info) {
199202
resolvedInfo = info;
203+
return promise;
200204
},
201205
},
202206
},
@@ -207,7 +211,7 @@ describe('Execute: Handles basic execution tasks', () => {
207211
const rootValue = { root: 'val' };
208212
const variableValues = { var: 'abc' };
209213

210-
executeSync({ schema, document, rootValue, variableValues });
214+
const result = execute({ schema, document, rootValue, variableValues });
211215

212216
const operation = document.definitions[0];
213217
assert(operation.kind === Kind.OPERATION_DEFINITION);
@@ -242,6 +246,10 @@ describe('Execute: Handles basic execution tasks', () => {
242246
});
243247

244248
expect(resolvedInfo.abortSignal).to.be.instanceOf(AbortSignal);
249+
250+
resolve();
251+
252+
await result;
245253
});
246254

247255
it('populates path correctly with complex types', () => {

0 commit comments

Comments
 (0)