diff --git a/src/execution/__tests__/nonnull-test.ts b/src/execution/__tests__/nonnull-test.ts index a136a2a4f08..d0b1b614b35 100644 --- a/src/execution/__tests__/nonnull-test.ts +++ b/src/execution/__tests__/nonnull-test.ts @@ -259,6 +259,16 @@ describe('Execute: handles non-nullable types', () => { path: ['syncNest', 'syncNest', 'sync'], locations: [{ line: 6, column: 22 }], }, + { + message: promiseError.message, + path: ['syncNest', 'promise'], + locations: [{ line: 5, column: 11 }], + }, + { + message: promiseError.message, + path: ['syncNest', 'syncNest', 'promise'], + locations: [{ line: 6, column: 27 }], + }, { message: syncError.message, path: ['syncNest', 'promiseNest', 'sync'], @@ -274,21 +284,6 @@ describe('Execute: handles non-nullable types', () => { path: ['promiseNest', 'syncNest', 'sync'], locations: [{ line: 12, column: 22 }], }, - { - message: promiseError.message, - path: ['syncNest', 'promise'], - locations: [{ line: 5, column: 11 }], - }, - { - message: promiseError.message, - path: ['syncNest', 'syncNest', 'promise'], - locations: [{ line: 6, column: 27 }], - }, - { - message: syncError.message, - path: ['promiseNest', 'promiseNest', 'sync'], - locations: [{ line: 13, column: 25 }], - }, { message: promiseError.message, path: ['syncNest', 'promiseNest', 'promise'], @@ -304,6 +299,11 @@ describe('Execute: handles non-nullable types', () => { path: ['promiseNest', 'syncNest', 'promise'], locations: [{ line: 12, column: 27 }], }, + { + message: syncError.message, + path: ['promiseNest', 'promiseNest', 'sync'], + locations: [{ line: 13, column: 25 }], + }, { message: promiseError.message, path: ['promiseNest', 'promiseNest', 'promise'], diff --git a/src/execution/__tests__/stream-test.ts b/src/execution/__tests__/stream-test.ts index b97a11fd333..b09cb7a39ce 100644 --- a/src/execution/__tests__/stream-test.ts +++ b/src/execution/__tests__/stream-test.ts @@ -1037,6 +1037,9 @@ describe('Execute: stream directive', () => { ], }, ], + hasNext: true, + }, + { hasNext: false, }, ]); @@ -1060,19 +1063,25 @@ describe('Execute: stream directive', () => { } /* c8 ignore stop */, }, }); - expectJSON(result).toDeepEqual({ - errors: [ - { - message: - 'Cannot return null for non-nullable field NestedObject.nonNullScalarField.', - locations: [{ line: 4, column: 11 }], - path: ['nestedObject', 'nonNullScalarField'], + expectJSON(result).toDeepEqual([ + { + errors: [ + { + message: + 'Cannot return null for non-nullable field NestedObject.nonNullScalarField.', + locations: [{ line: 4, column: 11 }], + path: ['nestedObject', 'nonNullScalarField'], + }, + ], + data: { + nestedObject: null, }, - ], - data: { - nestedObject: null, + hasNext: true, }, - }); + { + hasNext: false, + }, + ]); }); it('Filters payloads that are nulled by a later synchronous error', async () => { const document = parse(` @@ -1213,6 +1222,9 @@ describe('Execute: stream directive', () => { ], }, ], + hasNext: true, + }, + { hasNext: false, }, ]); diff --git a/src/execution/execute.ts b/src/execution/execute.ts index 2ca53936af7..4e3e34026d1 100644 --- a/src/execution/execute.ts +++ b/src/execution/execute.ts @@ -687,6 +687,7 @@ function executeField( ); // Get the resolve function, regardless of if its result is normal or abrupt (error). + let result: PromiseOrValue; try { // Build a JS object of arguments from the field.arguments AST, using the // variables scope to fulfill any variable references. @@ -702,50 +703,35 @@ function executeField( // used to represent an authenticated user, or request-specific caches. const contextValue = exeContext.contextValue; - const result = resolveFn(source, args, contextValue, info); - - let completed; - if (isPromise(result)) { - completed = result.then((resolved) => - completeValue( - exeContext, - returnType, - fieldNodes, - info, - path, - resolved, - asyncPayloadRecord, - ), - ); - } else { - completed = completeValue( - exeContext, - returnType, - fieldNodes, - info, - path, - result, - asyncPayloadRecord, - ); - } - - if (isPromise(completed)) { - // Note: we don't rely on a `catch` method, but we do expect "thenable" - // to take a second callback for the error case. - return completed.then(undefined, (rawError) => { - const error = locatedError(rawError, fieldNodes, pathToArray(path)); - const handledError = handleFieldError(error, returnType, errors); - filterSubsequentPayloads(exeContext, path, asyncPayloadRecord); - return handledError; - }); - } - return completed; + result = resolveFn(source, args, contextValue, info); } catch (rawError) { const error = locatedError(rawError, fieldNodes, pathToArray(path)); const handledError = handleFieldError(error, returnType, errors); filterSubsequentPayloads(exeContext, path, asyncPayloadRecord); return handledError; } + + if (isPromise(result)) { + return completePromiseCatchingErrors( + exeContext, + returnType, + fieldNodes, + info, + path, + result, + asyncPayloadRecord, + ); + } + + return completeValueCatchingErrors( + exeContext, + returnType, + fieldNodes, + info, + path, + result, + asyncPayloadRecord, + ); } /**