Skip to content

Commit

Permalink
Fix crash in node when mixing sync/async resolvers
Browse files Browse the repository at this point in the history
  • Loading branch information
chrskrchr committed Aug 18, 2022
1 parent 9a494d9 commit 7d48c7e
Show file tree
Hide file tree
Showing 2 changed files with 73 additions and 13 deletions.
50 changes: 50 additions & 0 deletions src/execution/__tests__/executor-test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ import { assert, expect } from 'chai';
import { describe, it } from 'mocha';

import { expectJSON } from '../../__testUtils__/expectJSON';
import { resolveOnNextTick } from '../../__testUtils__/resolveOnNextTick';

import { inspect } from '../../jsutils/inspect';

Expand Down Expand Up @@ -576,6 +577,55 @@ describe('Execute: Handles basic execution tasks', () => {
});
});

it('handles sync errors combined with rejections', async () => {
let isAsyncResolverCalled = false;

const schema = new GraphQLSchema({
query: new GraphQLObjectType({
name: 'Query',
fields: {
syncNullError: {
type: new GraphQLNonNull(GraphQLString),
resolve: () => null,
},
asyncNullError: {
type: new GraphQLNonNull(GraphQLString),
async resolve() {
await resolveOnNextTick();
await resolveOnNextTick();
await resolveOnNextTick();
isAsyncResolverCalled = true;
return Promise.resolve(null);
},
},
},
}),
});

// Order is important here, as the promise has to be created before the synchronous error is thrown
const document = parse(`
{
asyncNullError
syncNullError
}
`);

const result = await execute({ schema, document });

expect(isAsyncResolverCalled).to.equal(true);
expectJSON(result).toDeepEqual({
data: null,
errors: [
{
message:
'Cannot return null for non-nullable field Query.syncNullError.',
locations: [{ line: 4, column: 9 }],
path: ['syncNullError'],
},
],
});
});

it('Full response path is included for non-nullable fields', () => {
const A: GraphQLObjectType = new GraphQLObjectType({
name: 'A',
Expand Down
36 changes: 23 additions & 13 deletions src/execution/execute.ts
Original file line number Diff line number Diff line change
Expand Up @@ -431,22 +431,32 @@ function executeFields(
const results = Object.create(null);
let containsPromise = false;

for (const [responseName, fieldNodes] of fields) {
const fieldPath = addPath(path, responseName, parentType.name);
const result = executeField(
exeContext,
parentType,
sourceValue,
fieldNodes,
fieldPath,
);
try {
for (const [responseName, fieldNodes] of fields) {
const fieldPath = addPath(path, responseName, parentType.name);
const result = executeField(
exeContext,
parentType,
sourceValue,
fieldNodes,
fieldPath,
);

if (result !== undefined) {
results[responseName] = result;
if (isPromise(result)) {
containsPromise = true;
if (result !== undefined) {
results[responseName] = result;
if (isPromise(result)) {
containsPromise = true;
}
}
}
} catch (error) {
if (containsPromise) {
// Ensure that any promises returned by other fields are handled, as they may also reject.
return promiseForObject(results).finally(() => {
throw error;
});
}
throw error;
}

// If there are no promises, we can just return the object
Expand Down

0 comments on commit 7d48c7e

Please sign in to comment.