Skip to content

Commit cadcef8

Browse files
execute/subscribe: simplify to improve debugging experience (#2731)
Also move handling for error that not inherit from Error to `locatedError`
1 parent b790002 commit cadcef8

File tree

5 files changed

+150
-192
lines changed

5 files changed

+150
-192
lines changed

src/error/locatedError.d.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -5,12 +5,12 @@ import { ASTNode } from '../language/ast';
55
import { GraphQLError } from './GraphQLError';
66

77
/**
8-
* Given an arbitrary Error, presumably thrown while attempting to execute a
8+
* Given an arbitrary value, presumably thrown while attempting to execute a
99
* GraphQL operation, produce a new GraphQLError aware of the location in the
1010
* document responsible for the original Error.
1111
*/
1212
export function locatedError(
13-
originalError: Error | GraphQLError,
13+
rawOriginalError: any,
1414
nodes: ASTNode | ReadonlyArray<ASTNode> | undefined,
1515
path?: Maybe<ReadonlyArray<string | number>>,
1616
): GraphQLError;

src/error/locatedError.js

Lines changed: 11 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,19 +1,26 @@
1+
import inspect from '../jsutils/inspect';
2+
13
import type { ASTNode } from '../language/ast';
24

35
import { GraphQLError } from './GraphQLError';
46

57
/**
6-
* Given an arbitrary Error, presumably thrown while attempting to execute a
8+
* Given an arbitrary value, presumably thrown while attempting to execute a
79
* GraphQL operation, produce a new GraphQLError aware of the location in the
810
* document responsible for the original Error.
911
*/
1012
export function locatedError(
11-
originalError: Error | GraphQLError,
13+
rawOriginalError: mixed,
1214
nodes: ASTNode | $ReadOnlyArray<ASTNode> | void | null,
1315
path?: ?$ReadOnlyArray<string | number>,
1416
): GraphQLError {
15-
// Note: this uses a brand-check to support GraphQL errors originating from
16-
// other contexts.
17+
// Sometimes a non-error is thrown, wrap it as an Error instance to ensure a consistent Error interface.
18+
const originalError: Error | GraphQLError =
19+
rawOriginalError instanceof Error
20+
? rawOriginalError
21+
: new Error('Unexpected error value: ' + inspect(rawOriginalError));
22+
23+
// Note: this uses a brand-check to support GraphQL errors originating from other contexts.
1724
if (Array.isArray(originalError.path)) {
1825
return (originalError: any);
1926
}

src/execution/execute.d.ts

Lines changed: 0 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -159,21 +159,6 @@ export function buildResolveInfo(
159159
path: Path,
160160
): GraphQLResolveInfo;
161161

162-
/**
163-
* Isolates the "ReturnOrAbrupt" behavior to not de-opt the `resolveField`
164-
* function. Returns the result of resolveFn or the abrupt-return Error object.
165-
*
166-
* @internal
167-
*/
168-
export function resolveFieldValueOrError(
169-
exeContext: ExecutionContext,
170-
fieldDef: GraphQLField<any, any>,
171-
fieldNodes: ReadonlyArray<FieldNode>,
172-
resolveFn: GraphQLFieldResolver<any, any>,
173-
source: any,
174-
info: GraphQLResolveInfo,
175-
): any;
176-
177162
/**
178163
* If a resolveType function is not given, then a default resolve behavior is
179164
* used which attempts two strategies:

src/execution/execute.js

Lines changed: 70 additions & 106 deletions
Original file line numberDiff line numberDiff line change
@@ -386,8 +386,6 @@ function executeOperation(
386386
// Errors from sub-fields of a NonNull type may propagate to the top level,
387387
// at which point we still log the error and null the parent field, which
388388
// in this case is the entire response.
389-
//
390-
// Similar to completeValueCatchingError.
391389
try {
392390
const result =
393391
operation.operation === 'mutation'
@@ -643,6 +641,7 @@ function resolveField(
643641
return;
644642
}
645643

644+
const returnType = fieldDef.type;
646645
const resolveFn = fieldDef.resolve ?? exeContext.fieldResolver;
647646

648647
const info = buildResolveInfo(
@@ -653,67 +652,7 @@ function resolveField(
653652
path,
654653
);
655654

656-
// Get the resolve function, regardless of if its result is normal
657-
// or abrupt (error).
658-
const result = resolveFieldValueOrError(
659-
exeContext,
660-
fieldDef,
661-
fieldNodes,
662-
resolveFn,
663-
source,
664-
info,
665-
);
666-
667-
return completeValueCatchingError(
668-
exeContext,
669-
fieldDef.type,
670-
fieldNodes,
671-
info,
672-
path,
673-
result,
674-
);
675-
}
676-
677-
/**
678-
* @internal
679-
*/
680-
export function buildResolveInfo(
681-
exeContext: ExecutionContext,
682-
fieldDef: GraphQLField<mixed, mixed>,
683-
fieldNodes: $ReadOnlyArray<FieldNode>,
684-
parentType: GraphQLObjectType,
685-
path: Path,
686-
): GraphQLResolveInfo {
687-
// The resolve function's optional fourth argument is a collection of
688-
// information about the current execution state.
689-
return {
690-
fieldName: fieldDef.name,
691-
fieldNodes,
692-
returnType: fieldDef.type,
693-
parentType,
694-
path,
695-
schema: exeContext.schema,
696-
fragments: exeContext.fragments,
697-
rootValue: exeContext.rootValue,
698-
operation: exeContext.operation,
699-
variableValues: exeContext.variableValues,
700-
};
701-
}
702-
703-
/**
704-
* Isolates the "ReturnOrAbrupt" behavior to not de-opt the `resolveField`
705-
* function. Returns the result of resolveFn or the abrupt-return Error object.
706-
*
707-
* @internal
708-
*/
709-
export function resolveFieldValueOrError(
710-
exeContext: ExecutionContext,
711-
fieldDef: GraphQLField<mixed, mixed>,
712-
fieldNodes: $ReadOnlyArray<FieldNode>,
713-
resolveFn: GraphQLFieldResolver<mixed, mixed>,
714-
source: mixed,
715-
info: GraphQLResolveInfo,
716-
): Error | mixed {
655+
// Get the resolve function, regardless of if its result is normal or abrupt (error).
717656
try {
718657
// Build a JS object of arguments from the field.arguments AST, using the
719658
// variables scope to fulfill any variable references.
@@ -730,32 +669,7 @@ export function resolveFieldValueOrError(
730669
const contextValue = exeContext.contextValue;
731670

732671
const result = resolveFn(source, args, contextValue, info);
733-
return isPromise(result) ? result.then(undefined, asErrorInstance) : result;
734-
} catch (error) {
735-
return asErrorInstance(error);
736-
}
737-
}
738-
739-
// Sometimes a non-error is thrown, wrap it as an Error instance to ensure a
740-
// consistent Error interface.
741-
function asErrorInstance(error: mixed): Error {
742-
if (error instanceof Error) {
743-
return error;
744-
}
745-
return new Error('Unexpected error value: ' + inspect(error));
746-
}
747672

748-
// This is a small wrapper around completeValue which detects and logs errors
749-
// in the execution context.
750-
function completeValueCatchingError(
751-
exeContext: ExecutionContext,
752-
returnType: GraphQLOutputType,
753-
fieldNodes: $ReadOnlyArray<FieldNode>,
754-
info: GraphQLResolveInfo,
755-
path: Path,
756-
result: mixed,
757-
): PromiseOrValue<mixed> {
758-
try {
759673
let completed;
760674
if (isPromise(result)) {
761675
completed = result.then((resolved) =>
@@ -785,12 +699,34 @@ function completeValueCatchingError(
785699
}
786700
}
787701

788-
function handleFieldError(rawError, fieldNodes, path, returnType, exeContext) {
789-
const error = locatedError(
790-
asErrorInstance(rawError),
702+
/**
703+
* @internal
704+
*/
705+
export function buildResolveInfo(
706+
exeContext: ExecutionContext,
707+
fieldDef: GraphQLField<mixed, mixed>,
708+
fieldNodes: $ReadOnlyArray<FieldNode>,
709+
parentType: GraphQLObjectType,
710+
path: Path,
711+
): GraphQLResolveInfo {
712+
// The resolve function's optional fourth argument is a collection of
713+
// information about the current execution state.
714+
return {
715+
fieldName: fieldDef.name,
791716
fieldNodes,
792-
pathToArray(path),
793-
);
717+
returnType: fieldDef.type,
718+
parentType,
719+
path,
720+
schema: exeContext.schema,
721+
fragments: exeContext.fragments,
722+
rootValue: exeContext.rootValue,
723+
operation: exeContext.operation,
724+
variableValues: exeContext.variableValues,
725+
};
726+
}
727+
728+
function handleFieldError(rawError, fieldNodes, path, returnType, exeContext) {
729+
const error = locatedError(rawError, fieldNodes, pathToArray(path));
794730

795731
// If the field type is non-nullable, then it is resolved without any
796732
// protection from errors, however it still properly locates the error.
@@ -939,21 +875,49 @@ function completeListValue(
939875
const completedResults = arrayFrom(result, (item, index) => {
940876
// No need to modify the info object containing the path,
941877
// since from here on it is not ever accessed by resolver functions.
942-
const fieldPath = addPath(path, index, undefined);
943-
const completedItem = completeValueCatchingError(
944-
exeContext,
945-
itemType,
946-
fieldNodes,
947-
info,
948-
fieldPath,
949-
item,
950-
);
878+
const itemPath = addPath(path, index, undefined);
879+
try {
880+
let completedItem;
881+
if (isPromise(item)) {
882+
completedItem = item.then((resolved) =>
883+
completeValue(
884+
exeContext,
885+
itemType,
886+
fieldNodes,
887+
info,
888+
itemPath,
889+
resolved,
890+
),
891+
);
892+
} else {
893+
completedItem = completeValue(
894+
exeContext,
895+
itemType,
896+
fieldNodes,
897+
info,
898+
itemPath,
899+
item,
900+
);
901+
}
951902

952-
if (isPromise(completedItem)) {
953-
containsPromise = true;
903+
if (isPromise(completedItem)) {
904+
containsPromise = true;
905+
// Note: we don't rely on a `catch` method, but we do expect "thenable"
906+
// to take a second callback for the error case.
907+
return completedItem.then(undefined, (error) =>
908+
handleFieldError(error, fieldNodes, itemPath, itemType, exeContext),
909+
);
910+
}
911+
return completedItem;
912+
} catch (error) {
913+
return handleFieldError(
914+
error,
915+
fieldNodes,
916+
itemPath,
917+
itemType,
918+
exeContext,
919+
);
954920
}
955-
956-
return completedItem;
957921
});
958922

959923
return containsPromise ? Promise.all(completedResults) : completedResults;

0 commit comments

Comments
 (0)