Skip to content

Commit 50b3fef

Browse files
committed
perf: check if streamUsage is defined outside the loop
introducing separate looping functions for when streamUsage is defined
1 parent 868f8ae commit 50b3fef

File tree

1 file changed

+173
-7
lines changed

1 file changed

+173
-7
lines changed

src/execution/execute.ts

Lines changed: 173 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1034,7 +1034,6 @@ function getStreamUsage(
10341034

10351035
return streamUsage;
10361036
}
1037-
10381037
/**
10391038
* Complete a async iterator value by completing the result and calling
10401039
* recursively until all the results are completed.
@@ -1053,10 +1052,87 @@ async function completeAsyncIteratorValue(
10531052
const completedResults: Array<unknown> = [];
10541053
const acc: GraphQLResult<Array<unknown>> = [completedResults, []];
10551054
let index = 0;
1056-
const streamUsage = getStreamUsage(exeContext, fieldGroup, path);
10571055
// eslint-disable-next-line no-constant-condition
10581056
while (true) {
1059-
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+
if (iteration.done) {
1067+
break;
1068+
}
1069+
1070+
const item = iteration.value;
1071+
// TODO: add test case for asyncIterator returning a promise
1072+
/* c8 ignore start */
1073+
if (isPromise(item)) {
1074+
completedResults.push(
1075+
completePromisedListItemValue(
1076+
item,
1077+
acc,
1078+
exeContext,
1079+
itemType,
1080+
fieldGroup,
1081+
info,
1082+
itemPath,
1083+
incrementalContext,
1084+
deferMap,
1085+
),
1086+
);
1087+
containsPromise = true;
1088+
} else if (
1089+
/* c8 ignore stop */
1090+
completeListItemValue(
1091+
item,
1092+
completedResults,
1093+
acc,
1094+
exeContext,
1095+
itemType,
1096+
fieldGroup,
1097+
info,
1098+
itemPath,
1099+
incrementalContext,
1100+
deferMap,
1101+
)
1102+
) {
1103+
containsPromise = true;
1104+
}
1105+
index++;
1106+
}
1107+
1108+
return containsPromise
1109+
? Promise.all(completedResults).then((resolved) => [resolved, acc[1]])
1110+
: acc;
1111+
}
1112+
1113+
/**
1114+
* Complete a async iterator value by completing the result and calling
1115+
* recursively until all the results are completed.
1116+
*/
1117+
async function completeAsyncIteratorValueWithPossibleStream(
1118+
exeContext: ExecutionContext,
1119+
itemType: GraphQLOutputType,
1120+
fieldGroup: FieldGroup,
1121+
info: GraphQLResolveInfo,
1122+
path: Path,
1123+
asyncIterator: AsyncIterator<unknown>,
1124+
streamUsage: StreamUsage,
1125+
incrementalContext: IncrementalContext | undefined,
1126+
deferMap: ReadonlyMap<DeferUsage, DeferredFragmentRecord> | undefined,
1127+
): Promise<GraphQLResult<ReadonlyArray<unknown>>> {
1128+
let containsPromise = false;
1129+
const completedResults: Array<unknown> = [];
1130+
const acc: GraphQLResult<Array<unknown>> = [completedResults, []];
1131+
let index = 0;
1132+
const initialCount = streamUsage.initialCount;
1133+
// eslint-disable-next-line no-constant-condition
1134+
while (true) {
1135+
if (index >= initialCount) {
10601136
const streamRecord = new StreamRecord({
10611137
label: streamUsage.label,
10621138
path,
@@ -1167,17 +1243,32 @@ function completeListValue(
11671243
deferMap: ReadonlyMap<DeferUsage, DeferredFragmentRecord> | undefined,
11681244
): PromiseOrValue<GraphQLResult<ReadonlyArray<unknown>>> {
11691245
const itemType = returnType.ofType;
1246+
const streamUsage = getStreamUsage(exeContext, fieldGroup, path);
11701247

11711248
if (isAsyncIterable(result)) {
11721249
const asyncIterator = result[Symbol.asyncIterator]();
11731250

1174-
return completeAsyncIteratorValue(
1251+
if (streamUsage === undefined) {
1252+
return completeAsyncIteratorValue(
1253+
exeContext,
1254+
itemType,
1255+
fieldGroup,
1256+
info,
1257+
path,
1258+
asyncIterator,
1259+
incrementalContext,
1260+
deferMap,
1261+
);
1262+
}
1263+
1264+
return completeAsyncIteratorValueWithPossibleStream(
11751265
exeContext,
11761266
itemType,
11771267
fieldGroup,
11781268
info,
11791269
path,
11801270
asyncIterator,
1271+
streamUsage,
11811272
incrementalContext,
11821273
deferMap,
11831274
);
@@ -1189,13 +1280,27 @@ function completeListValue(
11891280
);
11901281
}
11911282

1192-
return completeIterableValue(
1283+
if (streamUsage === undefined) {
1284+
return completeIterableValue(
1285+
exeContext,
1286+
itemType,
1287+
fieldGroup,
1288+
info,
1289+
path,
1290+
result,
1291+
incrementalContext,
1292+
deferMap,
1293+
);
1294+
}
1295+
1296+
return completeIterableValueWithPossibleStream(
11931297
exeContext,
11941298
itemType,
11951299
fieldGroup,
11961300
info,
11971301
path,
11981302
result,
1303+
streamUsage,
11991304
incrementalContext,
12001305
deferMap,
12011306
);
@@ -1217,13 +1322,74 @@ function completeIterableValue(
12171322
const completedResults: Array<unknown> = [];
12181323
const acc: GraphQLResult<Array<unknown>> = [completedResults, []];
12191324
let index = 0;
1220-
const streamUsage = getStreamUsage(exeContext, fieldGroup, path);
1325+
for (const item of items) {
1326+
// No need to modify the info object containing the path,
1327+
// since from here on it is not ever accessed by resolver functions.
1328+
const itemPath = addPath(path, index, undefined);
1329+
1330+
if (isPromise(item)) {
1331+
completedResults.push(
1332+
completePromisedListItemValue(
1333+
item,
1334+
acc,
1335+
exeContext,
1336+
itemType,
1337+
fieldGroup,
1338+
info,
1339+
itemPath,
1340+
incrementalContext,
1341+
deferMap,
1342+
),
1343+
);
1344+
containsPromise = true;
1345+
} else if (
1346+
completeListItemValue(
1347+
item,
1348+
completedResults,
1349+
acc,
1350+
exeContext,
1351+
itemType,
1352+
fieldGroup,
1353+
info,
1354+
itemPath,
1355+
incrementalContext,
1356+
deferMap,
1357+
)
1358+
) {
1359+
containsPromise = true;
1360+
}
1361+
index++;
1362+
}
1363+
1364+
return containsPromise
1365+
? Promise.all(completedResults).then((resolved) => [resolved, acc[1]])
1366+
: acc;
1367+
}
1368+
1369+
function completeIterableValueWithPossibleStream(
1370+
exeContext: ExecutionContext,
1371+
itemType: GraphQLOutputType,
1372+
fieldGroup: FieldGroup,
1373+
info: GraphQLResolveInfo,
1374+
path: Path,
1375+
items: Iterable<unknown>,
1376+
streamUsage: StreamUsage,
1377+
incrementalContext: IncrementalContext | undefined,
1378+
deferMap: ReadonlyMap<DeferUsage, DeferredFragmentRecord> | undefined,
1379+
): PromiseOrValue<GraphQLResult<ReadonlyArray<unknown>>> {
1380+
// This is specified as a simple map, however we're optimizing the path
1381+
// where the list contains no Promises by avoiding creating another Promise.
1382+
let containsPromise = false;
1383+
const completedResults: Array<unknown> = [];
1384+
const acc: GraphQLResult<Array<unknown>> = [completedResults, []];
1385+
let index = 0;
1386+
const initialCount = streamUsage.initialCount;
12211387
const iterator = items[Symbol.iterator]();
12221388
let iteration = iterator.next();
12231389
while (!iteration.done) {
12241390
const item = iteration.value;
12251391

1226-
if (streamUsage && index >= streamUsage.initialCount) {
1392+
if (index >= initialCount) {
12271393
const streamRecord = new StreamRecord({
12281394
label: streamUsage.label,
12291395
path,

0 commit comments

Comments
 (0)