Skip to content

Commit 5bfb9d6

Browse files
committed
review changes
1 parent bc7049b commit 5bfb9d6

File tree

2 files changed

+54
-67
lines changed

2 files changed

+54
-67
lines changed

src/execution/__tests__/executor-test.ts

Lines changed: 42 additions & 46 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@ import { expect } from 'chai';
22
import { describe, it } from 'mocha';
33

44
import { expectJSON } from '../../__testUtils__/expectJSON';
5+
import { resolveOnNextTick } from '../../__testUtils__/resolveOnNextTick';
56

67
import { inspect } from '../../jsutils/inspect';
78
import { invariant } from '../../jsutils/invariant';
@@ -626,57 +627,52 @@ describe('Execute: Handles basic execution tasks', () => {
626627
});
627628

628629
it('handles sync errors combined with rejections', async () => {
629-
const catchUnhandledRejections = (reason: any) => {
630-
throw new Error('Unhandled rejection: ' + reason.message);
631-
};
632-
633-
process.on('unhandledRejection', catchUnhandledRejections);
630+
let isAsyncResolverCalled = false;
634631

635-
try {
636-
const schema = new GraphQLSchema({
637-
query: new GraphQLObjectType({
638-
name: 'Type',
639-
fields: {
640-
syncNullError: {
641-
type: new GraphQLNonNull(GraphQLString),
642-
resolve: () => null,
643-
},
644-
asyncNullError: {
645-
type: new GraphQLNonNull(GraphQLString),
646-
resolve: () => Promise.resolve(null),
632+
const schema = new GraphQLSchema({
633+
query: new GraphQLObjectType({
634+
name: 'Query',
635+
fields: {
636+
syncNullError: {
637+
type: new GraphQLNonNull(GraphQLString),
638+
resolve: () => null,
639+
},
640+
asyncNullError: {
641+
type: new GraphQLNonNull(GraphQLString),
642+
async resolve() {
643+
await resolveOnNextTick();
644+
await resolveOnNextTick();
645+
await resolveOnNextTick();
646+
isAsyncResolverCalled = true;
647+
return Promise.resolve(null);
647648
},
648649
},
649-
}),
650-
});
650+
},
651+
}),
652+
});
651653

652-
// Order is important here, as the promise has to be created before the synchronous error is thrown
653-
const document = parse(`
654-
{
655-
asyncNullError
656-
syncNullError
657-
}
658-
`);
659-
660-
const rootValue = {};
661-
662-
const result = await execute({ schema, document, rootValue });
663-
expectJSON(result).toDeepEqual({
664-
data: null,
665-
errors: [
666-
{
667-
message:
668-
'Cannot return null for non-nullable field Type.asyncNullError.',
669-
locations: [{ line: 3, column: 11 }],
670-
path: ['asyncNullError'],
671-
},
672-
],
673-
});
654+
// Order is important here, as the promise has to be created before the synchronous error is thrown
655+
const document = parse(`
656+
{
657+
asyncNullError
658+
syncNullError
659+
}
660+
`);
674661

675-
// Allow time for the asyncReject field to be rejected
676-
await new Promise((resolve) => process.nextTick(resolve));
677-
} finally {
678-
process.off('unhandledRejection', catchUnhandledRejections);
679-
}
662+
const result = await execute({ schema, document });
663+
664+
expect(isAsyncResolverCalled).to.equal(true);
665+
expectJSON(result).toDeepEqual({
666+
data: null,
667+
errors: [
668+
{
669+
message:
670+
'Cannot return null for non-nullable field Query.syncNullError.',
671+
locations: [{ line: 4, column: 9 }],
672+
path: ['syncNullError'],
673+
},
674+
],
675+
});
680676
});
681677

682678
it('Full response path is included for non-nullable fields', () => {

src/execution/execute.ts

Lines changed: 12 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -444,11 +444,10 @@ function executeFields(
444444
): PromiseOrValue<ObjMap<unknown>> {
445445
const results = Object.create(null);
446446
let containsPromise = false;
447-
let error: Error | undefined;
448447

449-
for (const [responseName, fieldNodes] of fields.entries()) {
450-
const fieldPath = addPath(path, responseName, parentType.name);
451-
try {
448+
try {
449+
for (const [responseName, fieldNodes] of fields.entries()) {
450+
const fieldPath = addPath(path, responseName, parentType.name);
452451
const result = executeField(
453452
exeContext,
454453
parentType,
@@ -463,34 +462,26 @@ function executeFields(
463462
containsPromise = true;
464463
}
465464
}
466-
} catch (err) {
467-
error = err;
468465
}
469-
470-
if (error) {
471-
break;
466+
} catch (error) {
467+
if (containsPromise) {
468+
// Ensure that any promises returned by other fields are handled, as they may also reject.
469+
return promiseForObject(results).finally(() => {
470+
throw error;
471+
});
472472
}
473+
throw error;
473474
}
474475

475476
// If there are no promises, we can just return the object
476477
if (!containsPromise) {
477-
if (error) {
478-
throw error;
479-
} else {
480-
return results;
481-
}
478+
return results;
482479
}
483480

484481
// Otherwise, results is a map from field name to the result of resolving that
485482
// field, which is possibly a promise. Return a promise that will return this
486483
// same map, but with any promises replaced with the values they resolved to.
487-
const promise = promiseForObject(results);
488-
if (error) {
489-
// Ensure that any promises returned by other fields are handled, as they may also reject.
490-
return promise.then(() => Promise.reject(error));
491-
}
492-
493-
return promise;
484+
return promiseForObject(results);
494485
}
495486

496487
/**

0 commit comments

Comments
 (0)