Skip to content

Commit f1a92a3

Browse files
committed
remove extra ticks from executeStreamIterator
by inlining executeStreamIteratorItem and using an async helper
1 parent 3ffb33c commit f1a92a3

File tree

2 files changed

+109
-112
lines changed

2 files changed

+109
-112
lines changed

src/execution/__tests__/stream-test.ts

Lines changed: 13 additions & 29 deletions
Original file line numberDiff line numberDiff line change
@@ -678,9 +678,6 @@ describe('Execute: stream directive', () => {
678678
path: ['friendList', 2],
679679
},
680680
],
681-
hasNext: true,
682-
},
683-
{
684681
hasNext: false,
685682
},
686683
]);
@@ -720,7 +717,7 @@ describe('Execute: stream directive', () => {
720717
}
721718
}
722719
`);
723-
const result = await completeAsync(document, 3, {
720+
const result = await completeAsync(document, 2, {
724721
async *friendList() {
725722
yield await Promise.resolve(friends[0]);
726723
yield await Promise.resolve(friends[1]);
@@ -749,10 +746,9 @@ describe('Execute: stream directive', () => {
749746
path: ['friendList', 2],
750747
},
751748
],
752-
hasNext: true,
749+
hasNext: false,
753750
},
754751
},
755-
{ done: false, value: { hasNext: false } },
756752
{ done: true, value: undefined },
757753
]);
758754
});
@@ -1214,9 +1210,6 @@ describe('Execute: stream directive', () => {
12141210
],
12151211
},
12161212
],
1217-
hasNext: true,
1218-
},
1219-
{
12201213
hasNext: false,
12211214
},
12221215
]);
@@ -1240,25 +1233,19 @@ describe('Execute: stream directive', () => {
12401233
} /* c8 ignore stop */,
12411234
},
12421235
});
1243-
expectJSON(result).toDeepEqual([
1244-
{
1245-
errors: [
1246-
{
1247-
message:
1248-
'Cannot return null for non-nullable field NestedObject.nonNullScalarField.',
1249-
locations: [{ line: 4, column: 11 }],
1250-
path: ['nestedObject', 'nonNullScalarField'],
1251-
},
1252-
],
1253-
data: {
1254-
nestedObject: null,
1236+
expectJSON(result).toDeepEqual({
1237+
errors: [
1238+
{
1239+
message:
1240+
'Cannot return null for non-nullable field NestedObject.nonNullScalarField.',
1241+
locations: [{ line: 4, column: 11 }],
1242+
path: ['nestedObject', 'nonNullScalarField'],
12551243
},
1256-
hasNext: true,
1257-
},
1258-
{
1259-
hasNext: false,
1244+
],
1245+
data: {
1246+
nestedObject: null,
12601247
},
1261-
]);
1248+
});
12621249
});
12631250
it('Filters payloads that are nulled by a later synchronous error', async () => {
12641251
const document = parse(`
@@ -1399,9 +1386,6 @@ describe('Execute: stream directive', () => {
13991386
],
14001387
},
14011388
],
1402-
hasNext: true,
1403-
},
1404-
{
14051389
hasNext: false,
14061390
},
14071391
]);

src/execution/execute.ts

Lines changed: 96 additions & 83 deletions
Original file line numberDiff line numberDiff line change
@@ -1950,59 +1950,49 @@ function executeStreamField(
19501950
return asyncPayloadRecord;
19511951
}
19521952

1953-
async function executeStreamIteratorItem(
1953+
async function completedItemsFromPromisedCompletedStreamedItem(
19541954
iterator: AsyncIterator<unknown>,
19551955
exeContext: ExecutionContext,
1956-
fieldNodes: ReadonlyArray<FieldNode>,
1957-
info: GraphQLResolveInfo,
19581956
itemType: GraphQLOutputType,
1959-
asyncPayloadRecord: StreamRecord,
1957+
fieldNodes: ReadonlyArray<FieldNode>,
1958+
path: Path,
19601959
itemPath: Path,
1961-
): Promise<IteratorResult<unknown>> {
1962-
let item;
1960+
completedItem: Promise<unknown>,
1961+
asyncPayloadRecord: AsyncPayloadRecord,
1962+
): Promise<[unknown] | null> {
19631963
try {
1964-
const { value, done } = await iterator.next();
1965-
if (done) {
1966-
asyncPayloadRecord.setIsCompletedIterator();
1967-
return { done, value: undefined };
1964+
try {
1965+
return [await completedItem];
1966+
} catch (rawError) {
1967+
const error = locatedError(rawError, fieldNodes, pathToArray(itemPath));
1968+
const handledError = handleFieldError(
1969+
error,
1970+
itemType,
1971+
asyncPayloadRecord.errors,
1972+
);
1973+
filterSubsequentPayloads(exeContext, itemPath, asyncPayloadRecord);
1974+
return [handledError];
19681975
}
1969-
item = value;
1970-
} catch (rawError) {
1971-
const error = locatedError(rawError, fieldNodes, pathToArray(itemPath));
1972-
const value = handleFieldError(error, itemType, asyncPayloadRecord.errors);
1973-
// don't continue if iterator throws
1974-
return { done: true, value };
1976+
} catch (error) {
1977+
handleStreamError(iterator, exeContext, path, asyncPayloadRecord, error);
1978+
return null;
19751979
}
1976-
let completedItem;
1977-
try {
1978-
completedItem = completeValue(
1979-
exeContext,
1980-
itemType,
1981-
fieldNodes,
1982-
info,
1983-
itemPath,
1984-
item,
1985-
asyncPayloadRecord,
1986-
);
1980+
}
19871981

1988-
if (isPromise(completedItem)) {
1989-
completedItem = completedItem.then(undefined, (rawError) => {
1990-
const error = locatedError(rawError, fieldNodes, pathToArray(itemPath));
1991-
const handledError = handleFieldError(
1992-
error,
1993-
itemType,
1994-
asyncPayloadRecord.errors,
1995-
);
1996-
filterSubsequentPayloads(exeContext, itemPath, asyncPayloadRecord);
1997-
return handledError;
1998-
});
1999-
}
2000-
return { done: false, value: completedItem };
2001-
} catch (rawError) {
2002-
const error = locatedError(rawError, fieldNodes, pathToArray(itemPath));
2003-
const value = handleFieldError(error, itemType, asyncPayloadRecord.errors);
2004-
filterSubsequentPayloads(exeContext, itemPath, asyncPayloadRecord);
2005-
return { done: false, value };
1982+
function handleStreamError(
1983+
iterator: AsyncIterator<unknown>,
1984+
exeContext: ExecutionContext,
1985+
path: Path,
1986+
asyncPayloadRecord: AsyncPayloadRecord,
1987+
error: GraphQLError,
1988+
): void {
1989+
asyncPayloadRecord.errors.push(error);
1990+
filterSubsequentPayloads(exeContext, path, asyncPayloadRecord);
1991+
// entire stream has errored and bubbled upwards
1992+
if (iterator?.return) {
1993+
iterator.return().catch(() => {
1994+
// ignore errors
1995+
});
20061996
}
20071997
}
20081998

@@ -2032,50 +2022,73 @@ async function executeStreamIterator(
20322022

20332023
let iteration;
20342024
try {
2035-
// eslint-disable-next-line no-await-in-loop
2036-
iteration = await executeStreamIteratorItem(
2037-
iterator,
2038-
exeContext,
2039-
fieldNodes,
2040-
info,
2041-
itemType,
2042-
asyncPayloadRecord,
2043-
itemPath,
2044-
);
2045-
} catch (error) {
2046-
asyncPayloadRecord.errors.push(error);
2047-
filterSubsequentPayloads(exeContext, path, asyncPayloadRecord);
2048-
asyncPayloadRecord.addItems(null);
2049-
// entire stream has errored and bubbled upwards
2050-
if (iterator?.return) {
2051-
iterator.return().catch(() => {
2052-
// ignore errors
2053-
});
2025+
try {
2026+
// eslint-disable-next-line no-await-in-loop
2027+
iteration = await iterator.next();
2028+
} catch (rawError) {
2029+
const error = locatedError(rawError, fieldNodes, pathToArray(itemPath));
2030+
const value = handleFieldError(
2031+
error,
2032+
itemType,
2033+
asyncPayloadRecord.errors,
2034+
);
2035+
// don't continue if iterator throws
2036+
asyncPayloadRecord.addItems([value]);
2037+
break;
20542038
}
2055-
return;
2056-
}
20572039

2058-
const { done, value: completedItem } = iteration;
2040+
const { done, value: item } = iteration;
20592041

2060-
let completedItems: PromiseOrValue<Array<unknown> | null>;
2061-
if (isPromise(completedItem)) {
2062-
completedItems = completedItem.then(
2063-
(value) => [value],
2064-
(error) => {
2065-
asyncPayloadRecord.errors.push(error);
2066-
filterSubsequentPayloads(exeContext, path, asyncPayloadRecord);
2067-
return null;
2068-
},
2069-
);
2070-
} else {
2071-
completedItems = [completedItem];
2072-
}
2042+
if (done) {
2043+
asyncPayloadRecord.setIsCompletedIterator();
2044+
asyncPayloadRecord.addItems([item]);
2045+
break;
2046+
}
20732047

2074-
asyncPayloadRecord.addItems(completedItems);
2048+
let completedItem;
2049+
try {
2050+
completedItem = completeValue(
2051+
exeContext,
2052+
itemType,
2053+
fieldNodes,
2054+
info,
2055+
itemPath,
2056+
item,
2057+
asyncPayloadRecord,
2058+
);
2059+
} catch (rawError) {
2060+
const error = locatedError(rawError, fieldNodes, pathToArray(itemPath));
2061+
completedItem = handleFieldError(
2062+
error,
2063+
itemType,
2064+
asyncPayloadRecord.errors,
2065+
);
2066+
filterSubsequentPayloads(exeContext, itemPath, asyncPayloadRecord);
2067+
}
20752068

2076-
if (done) {
2077-
break;
2069+
let completedItems: PromiseOrValue<Array<unknown> | null>;
2070+
if (isPromise(completedItem)) {
2071+
completedItems = completedItemsFromPromisedCompletedStreamedItem(
2072+
iterator,
2073+
exeContext,
2074+
itemType,
2075+
fieldNodes,
2076+
path,
2077+
itemPath,
2078+
completedItem,
2079+
asyncPayloadRecord,
2080+
);
2081+
} else {
2082+
completedItems = [completedItem];
2083+
}
2084+
2085+
asyncPayloadRecord.addItems(completedItems);
2086+
} catch (error) {
2087+
handleStreamError(iterator, exeContext, path, asyncPayloadRecord, error);
2088+
asyncPayloadRecord.addItems(null);
2089+
return;
20782090
}
2091+
20792092
previousAsyncPayloadRecord = asyncPayloadRecord;
20802093
index++;
20812094
}

0 commit comments

Comments
 (0)