Skip to content

Commit 1873373

Browse files
committed
perf: check if streamUsage is defined outside the loop
introducing separate looping functions for when streamUsage is defined
1 parent ce9b4ab commit 1873373

File tree

1 file changed

+188
-6
lines changed

1 file changed

+188
-6
lines changed

src/execution/execute.ts

Lines changed: 188 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1052,10 +1052,97 @@ async function completeAsyncIteratorValue(
10521052
[],
10531053
];
10541054
let index = 0;
1055-
const streamUsage = getStreamUsage(exeContext, fieldGroup, path);
10561055
// eslint-disable-next-line no-constant-condition
10571056
while (true) {
1058-
if (streamUsage && index >= streamUsage.initialCount) {
1057+
const itemPath = addPath(path, index, undefined);
1058+
let iteration;
1059+
try {
1060+
// eslint-disable-next-line no-await-in-loop
1061+
iteration = await asyncIterator.next();
1062+
} catch (rawError) {
1063+
throw locatedError(rawError, toNodes(fieldGroup), pathToArray(path));
1064+
}
1065+
1066+
// TODO: add test case for stream returning done before initialCount
1067+
/* c8 ignore next 3 */
1068+
if (iteration.done) {
1069+
break;
1070+
}
1071+
1072+
const item = iteration.value;
1073+
// TODO: add tests for stream backed by asyncIterator that returns a promise
1074+
/* c8 ignore start */
1075+
if (isPromise(item)) {
1076+
completedResults.push(
1077+
completePromisedListItemValue(
1078+
item,
1079+
graphqlWrappedResult,
1080+
exeContext,
1081+
itemType,
1082+
fieldGroup,
1083+
info,
1084+
itemPath,
1085+
incrementalContext,
1086+
deferMap,
1087+
),
1088+
);
1089+
containsPromise = true;
1090+
} else if (
1091+
/* c8 ignore stop */
1092+
completeListItemValue(
1093+
item,
1094+
completedResults,
1095+
graphqlWrappedResult,
1096+
exeContext,
1097+
itemType,
1098+
fieldGroup,
1099+
info,
1100+
itemPath,
1101+
incrementalContext,
1102+
deferMap,
1103+
)
1104+
// TODO: add tests for stream backed by asyncIterator that completes to a promise
1105+
/* c8 ignore start */
1106+
) {
1107+
containsPromise = true;
1108+
}
1109+
/* c8 ignore stop */
1110+
index++;
1111+
}
1112+
1113+
return containsPromise
1114+
? /* c8 ignore start */ Promise.all(completedResults).then((resolved) => [
1115+
resolved,
1116+
graphqlWrappedResult[1],
1117+
])
1118+
: /* c8 ignore stop */ graphqlWrappedResult;
1119+
}
1120+
1121+
/**
1122+
* Complete a async iterator value by completing the result and calling
1123+
* recursively until all the results are completed.
1124+
*/
1125+
async function completeAsyncIteratorValueWithPossibleStream(
1126+
exeContext: ExecutionContext,
1127+
itemType: GraphQLOutputType,
1128+
fieldGroup: FieldGroup,
1129+
info: GraphQLResolveInfo,
1130+
path: Path,
1131+
asyncIterator: AsyncIterator<unknown>,
1132+
streamUsage: StreamUsage,
1133+
incrementalContext: IncrementalContext | undefined,
1134+
deferMap: ReadonlyMap<DeferUsage, DeferredFragmentRecord> | undefined,
1135+
): Promise<GraphQLWrappedResult<ReadonlyArray<unknown>>> {
1136+
let containsPromise = false;
1137+
const completedResults: Array<unknown> = [];
1138+
const graphqlWrappedResult: GraphQLWrappedResult<Array<unknown>> = [
1139+
completedResults,
1140+
[],
1141+
];
1142+
let index = 0;
1143+
// eslint-disable-next-line no-constant-condition
1144+
while (true) {
1145+
if (index >= streamUsage.initialCount) {
10591146
const returnFn = asyncIterator.return;
10601147
let streamRecord: SubsequentResultRecord | CancellableStreamRecord;
10611148
if (returnFn === undefined) {
@@ -1166,17 +1253,32 @@ function completeListValue(
11661253
deferMap: ReadonlyMap<DeferUsage, DeferredFragmentRecord> | undefined,
11671254
): PromiseOrValue<GraphQLWrappedResult<ReadonlyArray<unknown>>> {
11681255
const itemType = returnType.ofType;
1256+
const streamUsage = getStreamUsage(exeContext, fieldGroup, path);
11691257

11701258
if (isAsyncIterable(result)) {
11711259
const asyncIterator = result[Symbol.asyncIterator]();
11721260

1173-
return completeAsyncIteratorValue(
1261+
if (streamUsage === undefined) {
1262+
return completeAsyncIteratorValue(
1263+
exeContext,
1264+
itemType,
1265+
fieldGroup,
1266+
info,
1267+
path,
1268+
asyncIterator,
1269+
incrementalContext,
1270+
deferMap,
1271+
);
1272+
}
1273+
1274+
return completeAsyncIteratorValueWithPossibleStream(
11741275
exeContext,
11751276
itemType,
11761277
fieldGroup,
11771278
info,
11781279
path,
11791280
asyncIterator,
1281+
streamUsage,
11801282
incrementalContext,
11811283
deferMap,
11821284
);
@@ -1188,13 +1290,27 @@ function completeListValue(
11881290
);
11891291
}
11901292

1191-
return completeIterableValue(
1293+
if (streamUsage === undefined) {
1294+
return completeIterableValue(
1295+
exeContext,
1296+
itemType,
1297+
fieldGroup,
1298+
info,
1299+
path,
1300+
result,
1301+
incrementalContext,
1302+
deferMap,
1303+
);
1304+
}
1305+
1306+
return completeIterableValueWithPossibleStream(
11921307
exeContext,
11931308
itemType,
11941309
fieldGroup,
11951310
info,
11961311
path,
11971312
result,
1313+
streamUsage,
11981314
incrementalContext,
11991315
deferMap,
12001316
);
@@ -1219,13 +1335,79 @@ function completeIterableValue(
12191335
[],
12201336
];
12211337
let index = 0;
1222-
const streamUsage = getStreamUsage(exeContext, fieldGroup, path);
1338+
for (const item of items) {
1339+
// No need to modify the info object containing the path,
1340+
// since from here on it is not ever accessed by resolver functions.
1341+
const itemPath = addPath(path, index, undefined);
1342+
1343+
if (isPromise(item)) {
1344+
completedResults.push(
1345+
completePromisedListItemValue(
1346+
item,
1347+
graphqlWrappedResult,
1348+
exeContext,
1349+
itemType,
1350+
fieldGroup,
1351+
info,
1352+
itemPath,
1353+
incrementalContext,
1354+
deferMap,
1355+
),
1356+
);
1357+
containsPromise = true;
1358+
} else if (
1359+
completeListItemValue(
1360+
item,
1361+
completedResults,
1362+
graphqlWrappedResult,
1363+
exeContext,
1364+
itemType,
1365+
fieldGroup,
1366+
info,
1367+
itemPath,
1368+
incrementalContext,
1369+
deferMap,
1370+
)
1371+
) {
1372+
containsPromise = true;
1373+
}
1374+
index++;
1375+
}
1376+
1377+
return containsPromise
1378+
? Promise.all(completedResults).then((resolved) => [
1379+
resolved,
1380+
graphqlWrappedResult[1],
1381+
])
1382+
: graphqlWrappedResult;
1383+
}
1384+
1385+
function completeIterableValueWithPossibleStream(
1386+
exeContext: ExecutionContext,
1387+
itemType: GraphQLOutputType,
1388+
fieldGroup: FieldGroup,
1389+
info: GraphQLResolveInfo,
1390+
path: Path,
1391+
items: Iterable<unknown>,
1392+
streamUsage: StreamUsage,
1393+
incrementalContext: IncrementalContext | undefined,
1394+
deferMap: ReadonlyMap<DeferUsage, DeferredFragmentRecord> | undefined,
1395+
): PromiseOrValue<GraphQLWrappedResult<ReadonlyArray<unknown>>> {
1396+
// This is specified as a simple map, however we're optimizing the path
1397+
// where the list contains no Promises by avoiding creating another Promise.
1398+
let containsPromise = false;
1399+
const completedResults: Array<unknown> = [];
1400+
const graphqlWrappedResult: GraphQLWrappedResult<Array<unknown>> = [
1401+
completedResults,
1402+
[],
1403+
];
1404+
let index = 0;
12231405
const iterator = items[Symbol.iterator]();
12241406
let iteration = iterator.next();
12251407
while (!iteration.done) {
12261408
const item = iteration.value;
12271409

1228-
if (streamUsage && index >= streamUsage.initialCount) {
1410+
if (index >= streamUsage.initialCount) {
12291411
const streamRecord: SubsequentResultRecord = {
12301412
label: streamUsage.label,
12311413
path,

0 commit comments

Comments
 (0)