diff --git a/src/execution/__tests__/abort-signal-test.ts b/src/execution/__tests__/abort-signal-test.ts index 6675b5c54d..ad9ba6c332 100644 --- a/src/execution/__tests__/abort-signal-test.ts +++ b/src/execution/__tests__/abort-signal-test.ts @@ -109,6 +109,53 @@ describe('Execute: Cancellation', () => { }); }); + it('should provide access to the abort signal within resolvers', async () => { + const abortController = new AbortController(); + const document = parse(` + query { + todo { + id + } + } + `); + + const cancellableAsyncFn = async (abortSignal: AbortSignal) => { + await resolveOnNextTick(); + abortSignal.throwIfAborted(); + }; + + const resultPromise = execute({ + document, + schema, + abortSignal: abortController.signal, + rootValue: { + todo: { + id: (_args: any, _context: any, _info: any, signal: AbortSignal) => + cancellableAsyncFn(signal), + }, + }, + }); + + abortController.abort(); + + const result = await resultPromise; + + expectJSON(result).toDeepEqual({ + data: { + todo: { + id: null, + }, + }, + errors: [ + { + message: 'This operation was aborted', + path: ['todo', 'id'], + locations: [{ line: 4, column: 11 }], + }, + ], + }); + }); + it('should stop the execution when aborted during object field completion with a custom error', async () => { const abortController = new AbortController(); const document = parse(` diff --git a/src/execution/execute.ts b/src/execution/execute.ts index 855cfcfed4..beef2fbb80 100644 --- a/src/execution/execute.ts +++ b/src/execution/execute.ts @@ -798,7 +798,7 @@ function executeField( deferMap: ReadonlyMap | undefined, ): PromiseOrValue> | undefined { const validatedExecutionArgs = exeContext.validatedExecutionArgs; - const { schema, contextValue, variableValues, hideSuggestions } = + const { schema, contextValue, variableValues, hideSuggestions, abortSignal } = validatedExecutionArgs; const fieldName = fieldDetailsList[0].node.name.value; const fieldDef = schema.getField(parentType, fieldName); @@ -833,7 +833,7 @@ function executeField( // The resolve function's optional third argument is a context value that // is provided to every resolve function within an execution. It is commonly // used to represent an authenticated user, or request-specific caches. - const result = resolveFn(source, args, contextValue, info); + const result = resolveFn(source, args, contextValue, info, abortSignal); if (isPromise(result)) { return completePromisedValue( @@ -1955,12 +1955,12 @@ export const defaultTypeResolver: GraphQLTypeResolver = * of calling that function while passing along args and context value. */ export const defaultFieldResolver: GraphQLFieldResolver = - function (source: any, args, contextValue, info) { + function (source: any, args, contextValue, info, abortSignal) { // ensure source is a value for which property access is acceptable. if (isObjectLike(source) || typeof source === 'function') { const property = source[info.fieldName]; if (typeof property === 'function') { - return source[info.fieldName](args, contextValue, info); + return source[info.fieldName](args, contextValue, info, abortSignal); } return property; } @@ -2115,6 +2115,7 @@ function executeSubscription( operation, variableValues, hideSuggestions, + abortSignal, } = validatedExecutionArgs; const rootType = schema.getSubscriptionType(); @@ -2180,7 +2181,7 @@ function executeSubscription( // The resolve function's optional third argument is a context value that // is provided to every resolve function within an execution. It is commonly // used to represent an authenticated user, or request-specific caches. - const result = resolveFn(rootValue, args, contextValue, info); + const result = resolveFn(rootValue, args, contextValue, info, abortSignal); if (isPromise(result)) { return result diff --git a/src/type/definition.ts b/src/type/definition.ts index 71db2e66a6..8d51201070 100644 --- a/src/type/definition.ts +++ b/src/type/definition.ts @@ -990,6 +990,7 @@ export type GraphQLFieldResolver< args: TArgs, context: TContext, info: GraphQLResolveInfo, + abortSignal: AbortSignal | undefined, ) => TResult; export interface GraphQLResolveInfo {