Skip to content

Commit 7158830

Browse files
committed
Catch unhandled exception in abstract resolution
1 parent 3de02c1 commit 7158830

File tree

2 files changed

+121
-1
lines changed

2 files changed

+121
-1
lines changed

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

Lines changed: 118 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,7 @@ import {
1212
import { GraphQLBoolean, GraphQLString } from '../../type/scalars';
1313
import { GraphQLSchema } from '../../type/schema';
1414

15-
import { executeSync } from '../execute';
15+
import { execute, executeSync } from '../execute';
1616

1717
class Dog {
1818
name: string;
@@ -154,6 +154,77 @@ odie.mother.progeny = [odie];
154154
const liz = new Person('Liz');
155155
const john = new Person('John', [garfield, odie], [liz, odie]);
156156

157+
const SearchableInterface = new GraphQLInterfaceType({
158+
name: 'Searchable',
159+
fields: {
160+
id: { type: GraphQLString },
161+
},
162+
});
163+
164+
const TypeA = new GraphQLObjectType({
165+
name: 'TypeA',
166+
interfaces: [SearchableInterface],
167+
fields: () => ({
168+
id: { type: GraphQLString },
169+
nameA: { type: GraphQLString },
170+
}),
171+
isTypeOf: (_value, _context, _info) => {
172+
return new Promise((_resolve, reject) => {
173+
setTimeout(() => {
174+
reject(new Error('TypeA_isTypeOf_rejected'));
175+
}, 10);
176+
});
177+
},
178+
});
179+
180+
const TypeB = new GraphQLObjectType({
181+
name: 'TypeB',
182+
interfaces: [SearchableInterface],
183+
fields: () => ({
184+
id: { type: GraphQLString },
185+
nameB: { type: GraphQLString },
186+
}),
187+
isTypeOf: (value: any, _context, _info) => {
188+
return value.id === 'b';
189+
},
190+
});
191+
192+
const queryTypeWithSearchable = new GraphQLObjectType({
193+
name: 'Query',
194+
fields: {
195+
person: {
196+
type: PersonType,
197+
resolve: () => john,
198+
},
199+
search: {
200+
type: SearchableInterface,
201+
args: { id: { type: GraphQLString } },
202+
resolve: (_source, { id }) => {
203+
/* c8 ignore start */
204+
if (id === 'a') {
205+
return { id: 'a', nameA: 'Object A' };
206+
/* c8 ignore end */
207+
} else if (id === 'b') {
208+
return { id: 'b', nameB: 'Object B' };
209+
}
210+
},
211+
},
212+
},
213+
});
214+
215+
const schemaWithSearchable = new GraphQLSchema({
216+
query: queryTypeWithSearchable,
217+
types: [
218+
PetType,
219+
TypeA,
220+
TypeB,
221+
SearchableInterface,
222+
PersonType,
223+
DogType,
224+
CatType,
225+
],
226+
});
227+
157228
describe('Execute: Union and intersection types', () => {
158229
it('can introspect on union and intersection types', () => {
159230
const document = parse(`
@@ -545,4 +616,50 @@ describe('Execute: Union and intersection types', () => {
545616
expect(encounteredRootValue).to.equal(rootValue);
546617
expect(encounteredContext).to.equal(contextValue);
547618
});
619+
620+
it('handles promises from isTypeOf correctly when a later type matches synchronously', async () => {
621+
const document = parse(`
622+
query TestSearch {
623+
search(id: "b") {
624+
__typename
625+
id
626+
... on TypeA {
627+
nameA
628+
}
629+
... on TypeB {
630+
nameB
631+
}
632+
}
633+
}
634+
`);
635+
636+
let unhandledRejection: any = null;
637+
/* c8 ignore start */
638+
const unhandledRejectionListener = (reason: any) => {
639+
unhandledRejection = reason;
640+
};
641+
process.on('unhandledRejection', unhandledRejectionListener);
642+
/* c8 ignore end */
643+
644+
const result = await execute({
645+
schema: schemaWithSearchable,
646+
document,
647+
});
648+
649+
expect(result.errors).to.be.undefined;
650+
expect(result.data).to.deep.equal({
651+
search: {
652+
__typename: 'TypeB',
653+
id: 'b',
654+
nameB: 'Object B',
655+
},
656+
});
657+
658+
// Give the TypeA promise a chance to reject and the listener to fire
659+
await new Promise((resolve) => setTimeout(resolve, 20));
660+
661+
process.removeListener('unhandledRejection', unhandledRejectionListener);
662+
663+
expect(unhandledRejection).to.be.null;
664+
});
548665
});

src/execution/execute.ts

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1002,6 +1002,9 @@ export const defaultTypeResolver: GraphQLTypeResolver<unknown, unknown> =
10021002
if (isPromise(isTypeOfResult)) {
10031003
promisedIsTypeOfResults[i] = isTypeOfResult;
10041004
} else if (isTypeOfResult) {
1005+
if (promisedIsTypeOfResults.length) {
1006+
Promise.allSettled(promisedIsTypeOfResults);
1007+
}
10051008
return type.name;
10061009
}
10071010
}

0 commit comments

Comments
 (0)