Skip to content

Commit e55c1a6

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

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
@@ -1014,7 +1014,6 @@ function getStreamUsage(
10141014

10151015
return streamUsage;
10161016
}
1017-
10181017
/**
10191018
* Complete a async iterator value by completing the result and calling
10201019
* recursively until all the results are completed.
@@ -1033,10 +1032,87 @@ async function completeAsyncIteratorValue(
10331032
const completedResults: Array<unknown> = [];
10341033
const acc: GraphQLResult<Array<unknown>> = [completedResults, []];
10351034
let index = 0;
1036-
const streamUsage = getStreamUsage(exeContext, fieldGroup, path);
10371035
// eslint-disable-next-line no-constant-condition
10381036
while (true) {
1039-
if (streamUsage && index >= streamUsage.initialCount) {
1037+
const itemPath = addPath(path, index, undefined);
1038+
let iteration;
1039+
try {
1040+
// eslint-disable-next-line no-await-in-loop
1041+
iteration = await asyncIterator.next();
1042+
} catch (rawError) {
1043+
throw locatedError(rawError, toNodes(fieldGroup), pathToArray(path));
1044+
}
1045+
1046+
if (iteration.done) {
1047+
break;
1048+
}
1049+
1050+
const item = iteration.value;
1051+
// TODO: add test case for asyncIterator returning a promise
1052+
/* c8 ignore start */
1053+
if (isPromise(item)) {
1054+
completedResults.push(
1055+
completePromisedListItemValue(
1056+
item,
1057+
acc,
1058+
exeContext,
1059+
itemType,
1060+
fieldGroup,
1061+
info,
1062+
itemPath,
1063+
incrementalContext,
1064+
deferMap,
1065+
),
1066+
);
1067+
containsPromise = true;
1068+
} else if (
1069+
/* c8 ignore stop */
1070+
completeListItemValue(
1071+
item,
1072+
completedResults,
1073+
acc,
1074+
exeContext,
1075+
itemType,
1076+
fieldGroup,
1077+
info,
1078+
itemPath,
1079+
incrementalContext,
1080+
deferMap,
1081+
)
1082+
) {
1083+
containsPromise = true;
1084+
}
1085+
index++;
1086+
}
1087+
1088+
return containsPromise
1089+
? Promise.all(completedResults).then((resolved) => [resolved, acc[1]])
1090+
: acc;
1091+
}
1092+
1093+
/**
1094+
* Complete a async iterator value by completing the result and calling
1095+
* recursively until all the results are completed.
1096+
*/
1097+
async function completeAsyncIteratorValueWithPossibleStream(
1098+
exeContext: ExecutionContext,
1099+
itemType: GraphQLOutputType,
1100+
fieldGroup: FieldGroup,
1101+
info: GraphQLResolveInfo,
1102+
path: Path,
1103+
asyncIterator: AsyncIterator<unknown>,
1104+
streamUsage: StreamUsage,
1105+
incrementalContext: IncrementalContext | undefined,
1106+
deferMap: ReadonlyMap<DeferUsage, DeferredFragmentRecord> | undefined,
1107+
): Promise<GraphQLResult<ReadonlyArray<unknown>>> {
1108+
let containsPromise = false;
1109+
const completedResults: Array<unknown> = [];
1110+
const acc: GraphQLResult<Array<unknown>> = [completedResults, []];
1111+
let index = 0;
1112+
const initialCount = streamUsage.initialCount;
1113+
// eslint-disable-next-line no-constant-condition
1114+
while (true) {
1115+
if (index >= initialCount) {
10401116
const streamRecord = new StreamRecord({
10411117
label: streamUsage.label,
10421118
path,
@@ -1147,17 +1223,32 @@ function completeListValue(
11471223
deferMap: ReadonlyMap<DeferUsage, DeferredFragmentRecord> | undefined,
11481224
): PromiseOrValue<GraphQLResult<ReadonlyArray<unknown>>> {
11491225
const itemType = returnType.ofType;
1226+
const streamUsage = getStreamUsage(exeContext, fieldGroup, path);
11501227

11511228
if (isAsyncIterable(result)) {
11521229
const asyncIterator = result[Symbol.asyncIterator]();
11531230

1154-
return completeAsyncIteratorValue(
1231+
if (streamUsage === undefined) {
1232+
return completeAsyncIteratorValue(
1233+
exeContext,
1234+
itemType,
1235+
fieldGroup,
1236+
info,
1237+
path,
1238+
asyncIterator,
1239+
incrementalContext,
1240+
deferMap,
1241+
);
1242+
}
1243+
1244+
return completeAsyncIteratorValueWithPossibleStream(
11551245
exeContext,
11561246
itemType,
11571247
fieldGroup,
11581248
info,
11591249
path,
11601250
asyncIterator,
1251+
streamUsage,
11611252
incrementalContext,
11621253
deferMap,
11631254
);
@@ -1169,13 +1260,27 @@ function completeListValue(
11691260
);
11701261
}
11711262

1172-
return completeIterableValue(
1263+
if (streamUsage === undefined) {
1264+
return completeIterableValue(
1265+
exeContext,
1266+
itemType,
1267+
fieldGroup,
1268+
info,
1269+
path,
1270+
result,
1271+
incrementalContext,
1272+
deferMap,
1273+
);
1274+
}
1275+
1276+
return completeIterableValueWithPossibleStream(
11731277
exeContext,
11741278
itemType,
11751279
fieldGroup,
11761280
info,
11771281
path,
11781282
result,
1283+
streamUsage,
11791284
incrementalContext,
11801285
deferMap,
11811286
);
@@ -1197,13 +1302,74 @@ function completeIterableValue(
11971302
const completedResults: Array<unknown> = [];
11981303
const acc: GraphQLResult<Array<unknown>> = [completedResults, []];
11991304
let index = 0;
1200-
const streamUsage = getStreamUsage(exeContext, fieldGroup, path);
1305+
for (const item of items) {
1306+
// No need to modify the info object containing the path,
1307+
// since from here on it is not ever accessed by resolver functions.
1308+
const itemPath = addPath(path, index, undefined);
1309+
1310+
if (isPromise(item)) {
1311+
completedResults.push(
1312+
completePromisedListItemValue(
1313+
item,
1314+
acc,
1315+
exeContext,
1316+
itemType,
1317+
fieldGroup,
1318+
info,
1319+
itemPath,
1320+
incrementalContext,
1321+
deferMap,
1322+
),
1323+
);
1324+
containsPromise = true;
1325+
} else if (
1326+
completeListItemValue(
1327+
item,
1328+
completedResults,
1329+
acc,
1330+
exeContext,
1331+
itemType,
1332+
fieldGroup,
1333+
info,
1334+
itemPath,
1335+
incrementalContext,
1336+
deferMap,
1337+
)
1338+
) {
1339+
containsPromise = true;
1340+
}
1341+
index++;
1342+
}
1343+
1344+
return containsPromise
1345+
? Promise.all(completedResults).then((resolved) => [resolved, acc[1]])
1346+
: acc;
1347+
}
1348+
1349+
function completeIterableValueWithPossibleStream(
1350+
exeContext: ExecutionContext,
1351+
itemType: GraphQLOutputType,
1352+
fieldGroup: FieldGroup,
1353+
info: GraphQLResolveInfo,
1354+
path: Path,
1355+
items: Iterable<unknown>,
1356+
streamUsage: StreamUsage,
1357+
incrementalContext: IncrementalContext | undefined,
1358+
deferMap: ReadonlyMap<DeferUsage, DeferredFragmentRecord> | undefined,
1359+
): PromiseOrValue<GraphQLResult<ReadonlyArray<unknown>>> {
1360+
// This is specified as a simple map, however we're optimizing the path
1361+
// where the list contains no Promises by avoiding creating another Promise.
1362+
let containsPromise = false;
1363+
const completedResults: Array<unknown> = [];
1364+
const acc: GraphQLResult<Array<unknown>> = [completedResults, []];
1365+
let index = 0;
1366+
const initialCount = streamUsage.initialCount;
12011367
const iterator = items[Symbol.iterator]();
12021368
let iteration = iterator.next();
12031369
while (!iteration.done) {
12041370
const item = iteration.value;
12051371

1206-
if (streamUsage && index >= streamUsage.initialCount) {
1372+
if (index >= initialCount) {
12071373
const streamRecord = new StreamRecord({
12081374
label: streamUsage.label,
12091375
path,

0 commit comments

Comments
 (0)