diff --git a/src/execution/__tests__/nonnull-test.ts b/src/execution/__tests__/nonnull-test.ts index 12b223a622d..d8a8c4e5924 100644 --- a/src/execution/__tests__/nonnull-test.ts +++ b/src/execution/__tests__/nonnull-test.ts @@ -259,16 +259,6 @@ 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'], @@ -284,6 +274,21 @@ 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'], @@ -299,11 +304,6 @@ 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/execute.ts b/src/execution/execute.ts index 75fa22c66d9..831cf384870 100644 --- a/src/execution/execute.ts +++ b/src/execution/execute.ts @@ -763,15 +763,33 @@ function executeField( const result = resolveFn(source, args, contextValue, info); if (isPromise(result)) { - return completePromisedValue( - exeContext, - returnType, - fieldGroup, - info, - path, - result, - incrementalContext, - deferMap, + return ( + result + .then((resolved) => + completeValue( + exeContext, + returnType, + fieldGroup, + info, + path, + resolved, + incrementalContext, + deferMap, + ), + ) + // Note: we don't rely on a `catch` method, but we do expect "thenable" + // to take a second callback for the error case. + .then(undefined, (rawError) => { + handleFieldError( + rawError, + exeContext, + returnType, + fieldGroup, + path, + incrementalContext, + ); + return null; + }) ); } @@ -987,46 +1005,6 @@ function completeValue( ); } -async function completePromisedValue( - exeContext: ExecutionContext, - returnType: GraphQLOutputType, - fieldGroup: FieldGroup, - info: GraphQLResolveInfo, - path: Path, - result: Promise, - incrementalContext: IncrementalContext | undefined, - deferMap: ReadonlyMap | undefined, -): Promise { - try { - const resolved = await result; - let completed = completeValue( - exeContext, - returnType, - fieldGroup, - info, - path, - resolved, - incrementalContext, - deferMap, - ); - - if (isPromise(completed)) { - completed = await completed; - } - return completed; - } catch (rawError) { - handleFieldError( - rawError, - exeContext, - returnType, - fieldGroup, - path, - incrementalContext, - ); - return null; - } -} - /** * Returns an object containing info for streaming if a field should be * streamed based on the experimental flag, stream directive present and @@ -1464,16 +1442,32 @@ function completeListItemValue( ): boolean { if (isPromise(item)) { completedResults.push( - completePromisedValue( - exeContext, - itemType, - fieldGroup, - info, - itemPath, - item, - incrementalContext, - deferMap, - ), + item + .then((resolved) => + completeValue( + exeContext, + itemType, + fieldGroup, + info, + itemPath, + resolved, + incrementalContext, + deferMap, + ), + ) + // Note: we don't rely on a `catch` method, but we do expect "thenable" + // to take a second callback for the error case. + .then(undefined, (rawError) => { + handleFieldError( + rawError, + exeContext, + itemType, + fieldGroup, + itemPath, + incrementalContext, + ); + return null; + }), ); return true; } @@ -2510,23 +2504,42 @@ function completeStreamItems( itemType: GraphQLOutputType, ): PromiseOrValue { if (isPromise(item)) { - return completePromisedValue( - exeContext, - itemType, - fieldGroup, - info, - itemPath, - item, - incrementalContext, - new Map(), - ).then( - (resolvedItem) => - buildStreamItemsResult(incrementalContext, streamRecord, resolvedItem), - (error) => ({ - streamRecord, - errors: withError(incrementalContext.errors, error), - }), - ); + return item + .then((resolved) => + completeValue( + exeContext, + itemType, + fieldGroup, + info, + itemPath, + resolved, + incrementalContext, + new Map(), + ), + ) + .then(undefined, (rawError) => { + handleFieldError( + rawError, + exeContext, + itemType, + fieldGroup, + itemPath, + incrementalContext, + ); + return null; + }) + .then( + (resolvedItem) => + buildStreamItemsResult( + incrementalContext, + streamRecord, + resolvedItem, + ), + (error) => ({ + streamRecord, + errors: withError(incrementalContext.errors, error), + }), + ); } let completedItem;