Skip to content

Commit c88af4e

Browse files
JoviDeCroockbenjie
authored andcommitted
Catch unhandled exception in abstract resolution (#4392)
Co-authored-by: Benjie <[email protected]>
1 parent eb81376 commit c88af4e

File tree

3 files changed

+121
-7
lines changed

3 files changed

+121
-7
lines changed

.c8rc.json

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,8 @@
77
"src/jsutils/Maybe.ts",
88
"src/jsutils/ObjMap.ts",
99
"src/jsutils/PromiseOrValue.ts",
10-
"src/utilities/typedQueryDocumentNode.ts"
10+
"src/utilities/typedQueryDocumentNode.ts",
11+
"src/**/__tests__/**/*.ts"
1112
],
1213
"clean": true,
1314
"report-dir": "reports/coverage",

src/execution/__tests__/union-interface-test.ts

Lines changed: 112 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -143,7 +143,7 @@ const PetType = new GraphQLUnionType({
143143
if (value instanceof Cat) {
144144
return CatType.name;
145145
}
146-
/* c8 ignore next 3 */
146+
147147
// Not reachable, all possible types have been considered.
148148
expect.fail('Not reachable');
149149
},
@@ -191,6 +191,70 @@ const john = new Person(
191191
[garfield, fern],
192192
);
193193

194+
const SearchableInterface = new GraphQLInterfaceType({
195+
name: 'Searchable',
196+
fields: {
197+
id: { type: GraphQLString },
198+
},
199+
});
200+
201+
const TypeA = new GraphQLObjectType({
202+
name: 'TypeA',
203+
interfaces: [SearchableInterface],
204+
fields: () => ({
205+
id: { type: GraphQLString },
206+
nameA: { type: GraphQLString },
207+
}),
208+
isTypeOf: (_value, _context, _info) =>
209+
new Promise((_resolve, reject) =>
210+
setTimeout(() => reject(new Error('TypeA_isTypeOf_rejected')), 10),
211+
),
212+
});
213+
214+
const TypeB = new GraphQLObjectType({
215+
name: 'TypeB',
216+
interfaces: [SearchableInterface],
217+
fields: () => ({
218+
id: { type: GraphQLString },
219+
nameB: { type: GraphQLString },
220+
}),
221+
isTypeOf: (value: any, _context, _info) => value.id === 'b',
222+
});
223+
224+
const queryTypeWithSearchable = new GraphQLObjectType({
225+
name: 'Query',
226+
fields: {
227+
person: {
228+
type: PersonType,
229+
resolve: () => john,
230+
},
231+
search: {
232+
type: SearchableInterface,
233+
args: { id: { type: GraphQLString } },
234+
resolve: (_source, { id }) => {
235+
if (id === 'a') {
236+
return { id: 'a', nameA: 'Object A' };
237+
} else if (id === 'b') {
238+
return { id: 'b', nameB: 'Object B' };
239+
}
240+
},
241+
},
242+
},
243+
});
244+
245+
const schemaWithSearchable = new GraphQLSchema({
246+
query: queryTypeWithSearchable,
247+
types: [
248+
PetType,
249+
TypeA,
250+
TypeB,
251+
SearchableInterface,
252+
PersonType,
253+
DogType,
254+
CatType,
255+
],
256+
});
257+
194258
describe('Execute: Union and intersection types', () => {
195259
it('can introspect on union and intersection types', () => {
196260
const document = parse(`
@@ -633,4 +697,51 @@ describe('Execute: Union and intersection types', () => {
633697
},
634698
});
635699
});
700+
701+
it('handles promises from isTypeOf correctly when a later type matches synchronously', async () => {
702+
const document = parse(`
703+
query TestSearch {
704+
search(id: "b") {
705+
__typename
706+
id
707+
... on TypeA {
708+
nameA
709+
}
710+
... on TypeB {
711+
nameB
712+
}
713+
}
714+
}
715+
`);
716+
717+
let unhandledRejection: any = null;
718+
const unhandledRejectionListener = (reason: any) => {
719+
unhandledRejection = reason;
720+
};
721+
// eslint-disable-next-line
722+
process.on('unhandledRejection', unhandledRejectionListener);
723+
724+
const result = await execute({
725+
schema: schemaWithSearchable,
726+
document,
727+
});
728+
729+
expect(result.errors).to.equal(undefined);
730+
expect(result.data).to.deep.equal({
731+
search: {
732+
__typename: 'TypeB',
733+
id: 'b',
734+
nameB: 'Object B',
735+
},
736+
});
737+
738+
// Give the TypeA promise a chance to reject and the listener to fire
739+
740+
await new Promise((resolve) => setTimeout(resolve, 20));
741+
742+
// eslint-disable-next-line
743+
process.removeListener('unhandledRejection', unhandledRejectionListener);
744+
745+
expect(unhandledRejection).to.equal(null);
746+
});
636747
});

src/execution/execute.ts

Lines changed: 7 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -2048,12 +2048,14 @@ export const defaultTypeResolver: GraphQLTypeResolver<unknown, unknown> =
20482048
if (isPromise(isTypeOfResult)) {
20492049
promisedIsTypeOfResults[i] = isTypeOfResult;
20502050
} else if (isTypeOfResult) {
2051-
if (promisedIsTypeOfResults.length > 0) {
2052-
Promise.all(promisedIsTypeOfResults).then(undefined, () => {
2053-
/* ignore errors */
2054-
});
2051+
if (promisedIsTypeOfResults.length) {
2052+
// Explicitly ignore any promise rejections
2053+
Promise.allSettled(promisedIsTypeOfResults)
2054+
/* c8 ignore next 3 */
2055+
.catch(() => {
2056+
// Do nothing
2057+
});
20552058
}
2056-
20572059
return type.name;
20582060
}
20592061
}

0 commit comments

Comments
 (0)