Skip to content

Commit 3b90551

Browse files
authored
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 fe104be commit 3b90551

2 files changed

Lines changed: 35 additions & 26 deletions

File tree

src/execution/Executor.ts

Lines changed: 25 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -377,9 +377,8 @@ export interface StreamItemResult {
377377
export class Executor {
378378
validatedExecutionArgs: ValidatedExecutionArgs;
379379
deferUsageSet?: DeferUsageSet | undefined;
380-
onExternalAbort: (() => void) | undefined;
381380
finished: boolean;
382-
abortControllers: Set<AbortController>;
381+
resolverAbortControllers: Set<AbortController>;
383382
collectedErrors: CollectedErrors;
384383
groups: Array<DeliveryGroup>;
385384
tasks: Array<ExecutionGroup>;
@@ -403,9 +402,8 @@ export class Executor {
403402
) {
404403
this.validatedExecutionArgs = validatedExecutionArgs;
405404
this.deferUsageSet = deferUsageSet;
406-
this.onExternalAbort = undefined;
407405
this.finished = false;
408-
this.abortControllers = new Set();
406+
this.resolverAbortControllers = new Set();
409407
this.collectedErrors = new CollectedErrors();
410408
this.groups = [];
411409
this.tasks = [];
@@ -439,17 +437,22 @@ export class Executor {
439437
> {
440438
const validatedExecutionArgs = this.validatedExecutionArgs;
441439
const externalAbortSignal = validatedExecutionArgs.externalAbortSignal;
440+
let removeAbortListener: (() => void) | undefined;
442441
if (externalAbortSignal) {
443442
if (externalAbortSignal.aborted) {
444443
throw new Error(externalAbortSignal.reason);
445444
}
446-
const onExternalAbort = () => {
447-
this.cancel(externalAbortSignal.reason);
448-
};
445+
const onExternalAbort = () => this.cancel(externalAbortSignal.reason);
446+
removeAbortListener = () =>
447+
externalAbortSignal.removeEventListener('abort', onExternalAbort);
449448
externalAbortSignal.addEventListener('abort', onExternalAbort);
450-
this.onExternalAbort = onExternalAbort;
451449
}
452450

451+
const onFinish = () => {
452+
removeAbortListener?.();
453+
this.finish();
454+
};
455+
453456
try {
454457
const {
455458
schema,
@@ -490,11 +493,11 @@ export class Executor {
490493
if (isPromise(result)) {
491494
const promise = result.then(
492495
(data) => {
493-
this.finish();
496+
onFinish();
494497
return this.buildResponse(data);
495498
},
496499
(error: unknown) => {
497-
this.finish();
500+
onFinish();
498501
this.collectedErrors.add(error as GraphQLError, undefined);
499502
return this.buildResponse(null);
500503
},
@@ -503,9 +506,11 @@ export class Executor {
503506
? cancellablePromise(promise, externalAbortSignal)
504507
: promise;
505508
}
509+
onFinish();
506510
return this.buildResponse(result);
507511
} catch (error) {
508512
this.collectedErrors.add(error as GraphQLError, undefined);
513+
onFinish();
509514
return this.buildResponse(null);
510515
}
511516
}
@@ -523,24 +528,20 @@ export class Executor {
523528
}
524529

525530
finish(reason?: unknown): void {
526-
if (this.finished) {
527-
return;
531+
if (!this.finished) {
532+
this.finished = true;
533+
this.triggerResolverAbortSignals(reason);
528534
}
529-
this.finished = true;
530-
const { abortControllers, onExternalAbort } = this;
535+
}
536+
537+
triggerResolverAbortSignals(reason?: unknown): void {
538+
const { resolverAbortControllers } = this;
531539
const finishReason =
532540
reason ?? new Error('Execution has already completed.');
533-
for (const abortController of abortControllers) {
541+
for (const abortController of resolverAbortControllers) {
534542
abortController.abort(finishReason);
535543
}
536-
if (onExternalAbort) {
537-
this.validatedExecutionArgs.externalAbortSignal?.removeEventListener(
538-
'abort',
539-
onExternalAbort,
540-
);
541-
}
542544
}
543-
544545
/**
545546
* Given a completed execution context and data, build the `{ errors, data }`
546547
* response defined by the "Response" section of the GraphQL specification.
@@ -788,11 +789,11 @@ export class Executor {
788789
throw new Error('Execution has already completed.');
789790
}
790791
const abortController = new AbortController();
791-
this.abortControllers.add(abortController);
792+
this.resolverAbortControllers.add(abortController);
792793
return {
793794
abortSignal: abortController.signal,
794795
unregister: () => {
795-
this.abortControllers.delete(abortController);
796+
this.resolverAbortControllers.delete(abortController);
796797
},
797798
};
798799
},

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';
@@ -192,7 +193,9 @@ describe('Execute: Handles basic execution tasks', () => {
192193
});
193194
});
194195

195-
it('provides info about current execution state', () => {
196+
it('provides info about current execution state', async () => {
197+
// eslint-disable-next-line @typescript-eslint/no-invalid-void-type
198+
const { promise, resolve } = promiseWithResolvers<void>();
196199
let resolvedInfo: GraphQLResolveInfo | undefined;
197200
const testType = new GraphQLObjectType({
198201
name: 'Test',
@@ -201,6 +204,7 @@ describe('Execute: Handles basic execution tasks', () => {
201204
type: GraphQLString,
202205
resolve(_val, _args, _ctx, info) {
203206
resolvedInfo = info;
207+
return promise;
204208
},
205209
},
206210
},
@@ -211,7 +215,7 @@ describe('Execute: Handles basic execution tasks', () => {
211215
const rootValue = { root: 'val' };
212216
const variableValues = { var: 'abc' };
213217

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

216220
const operation = document.definitions[0];
217221
assert(operation.kind === Kind.OPERATION_DEFINITION);
@@ -246,6 +250,10 @@ describe('Execute: Handles basic execution tasks', () => {
246250
});
247251

248252
expect(resolvedInfo.abortSignal).to.be.instanceOf(AbortSignal);
253+
254+
resolve();
255+
256+
await result;
249257
});
250258

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

0 commit comments

Comments
 (0)