Skip to content

Commit 4865143

Browse files
committed
optimize some more
1 parent 9951617 commit 4865143

File tree

1 file changed

+167
-13
lines changed

1 file changed

+167
-13
lines changed

src/execution/execute.ts

+167-13
Original file line numberDiff line numberDiff line change
@@ -1104,13 +1104,68 @@ async function completeAsyncIteratorValue(
11041104
incrementalContext: IncrementalContext | undefined,
11051105
deferMap: ReadonlyMap<DeferUsage, DeferredFragmentRecord> | undefined,
11061106
): Promise<ReadonlyArray<unknown>> {
1107-
const streamUsage = getStreamUsage(exeContext, fieldGroup, path);
11081107
let containsPromise = false;
11091108
const completedResults: Array<unknown> = [];
11101109
let index = 0;
11111110
// eslint-disable-next-line no-constant-condition
11121111
while (true) {
1113-
if (streamUsage && index >= streamUsage.initialCount) {
1112+
const itemPath = addPath(path, index, undefined);
1113+
let iteration;
1114+
try {
1115+
// eslint-disable-next-line no-await-in-loop
1116+
iteration = await asyncIterator.next();
1117+
} catch (rawError) {
1118+
throw locatedError(rawError, toNodes(fieldGroup), pathToArray(path));
1119+
}
1120+
1121+
if (iteration.done) {
1122+
break;
1123+
}
1124+
1125+
if (
1126+
completeListItemValue(
1127+
iteration.value,
1128+
completedResults,
1129+
exeContext,
1130+
itemType,
1131+
fieldGroup,
1132+
info,
1133+
itemPath,
1134+
incrementalContext,
1135+
deferMap,
1136+
)
1137+
) {
1138+
containsPromise = true;
1139+
}
1140+
1141+
index++;
1142+
}
1143+
1144+
return containsPromise ? Promise.all(completedResults) : completedResults;
1145+
}
1146+
1147+
/**
1148+
* Complete a async iterator value by completing the result and calling
1149+
* recursively until all the results are completed.
1150+
*/
1151+
async function completeAsyncIteratorValueWithPossibleStream(
1152+
exeContext: ExecutionContext,
1153+
itemType: GraphQLOutputType,
1154+
fieldGroup: FieldGroup,
1155+
info: GraphQLResolveInfo,
1156+
path: Path,
1157+
asyncIterator: AsyncIterator<unknown>,
1158+
streamUsage: StreamUsage,
1159+
incrementalContext: IncrementalContext | undefined,
1160+
deferMap: ReadonlyMap<DeferUsage, DeferredFragmentRecord> | undefined,
1161+
): Promise<ReadonlyArray<unknown>> {
1162+
let containsPromise = false;
1163+
const completedResults: Array<unknown> = [];
1164+
let index = 0;
1165+
const initialCount = streamUsage.initialCount;
1166+
// eslint-disable-next-line no-constant-condition
1167+
while (true) {
1168+
if (index >= initialCount) {
11141169
const streamRecord = new StreamRecord({
11151170
label: streamUsage.label,
11161171
path,
@@ -1151,13 +1206,16 @@ async function completeAsyncIteratorValue(
11511206
try {
11521207
// eslint-disable-next-line no-await-in-loop
11531208
iteration = await asyncIterator.next();
1154-
if (iteration.done) {
1155-
break;
1156-
}
11571209
} catch (rawError) {
11581210
throw locatedError(rawError, toNodes(fieldGroup), pathToArray(path));
11591211
}
11601212

1213+
// TODO: add test case for stream returning done before initialCount
1214+
/* c8 ignore next 3 */
1215+
if (iteration.done) {
1216+
break;
1217+
}
1218+
11611219
if (
11621220
completeListItemValue(
11631221
iteration.value,
@@ -1169,14 +1227,17 @@ async function completeAsyncIteratorValue(
11691227
itemPath,
11701228
incrementalContext,
11711229
deferMap,
1172-
)
1230+
) /* c8 ignore start */
11731231
) {
1232+
// TODO: add test case for asyncIterator that yields promises
11741233
containsPromise = true;
1175-
}
1176-
index += 1;
1234+
} /* c8 ignore stop */
1235+
index++;
11771236
}
11781237

1179-
return containsPromise ? Promise.all(completedResults) : completedResults;
1238+
return containsPromise
1239+
? /* c8 ignore start */ Promise.all(completedResults)
1240+
: /* c8 ignore stop */ completedResults;
11801241
}
11811242

11821243
function addIncrementalDataRecord(
@@ -1206,17 +1267,32 @@ function completeListValue(
12061267
deferMap: ReadonlyMap<DeferUsage, DeferredFragmentRecord> | undefined,
12071268
): PromiseOrValue<ReadonlyArray<unknown>> {
12081269
const itemType = returnType.ofType;
1270+
const streamUsage = getStreamUsage(exeContext, fieldGroup, path);
12091271

12101272
if (isAsyncIterable(result)) {
12111273
const asyncIterator = result[Symbol.asyncIterator]();
12121274

1213-
return completeAsyncIteratorValue(
1275+
if (streamUsage === undefined) {
1276+
return completeAsyncIteratorValue(
1277+
exeContext,
1278+
itemType,
1279+
fieldGroup,
1280+
info,
1281+
path,
1282+
asyncIterator,
1283+
incrementalContext,
1284+
deferMap,
1285+
);
1286+
}
1287+
1288+
return completeAsyncIteratorValueWithPossibleStream(
12141289
exeContext,
12151290
itemType,
12161291
fieldGroup,
12171292
info,
12181293
path,
12191294
asyncIterator,
1295+
streamUsage,
12201296
incrementalContext,
12211297
deferMap,
12221298
);
@@ -1228,19 +1304,97 @@ function completeListValue(
12281304
);
12291305
}
12301306

1231-
const streamUsage = getStreamUsage(exeContext, fieldGroup, path);
1307+
if (streamUsage === undefined) {
1308+
return completeIterableValue(
1309+
exeContext,
1310+
itemType,
1311+
fieldGroup,
1312+
info,
1313+
path,
1314+
result,
1315+
incrementalContext,
1316+
deferMap,
1317+
);
1318+
}
1319+
1320+
return completeIterableValueWithPossibleStream(
1321+
exeContext,
1322+
itemType,
1323+
fieldGroup,
1324+
info,
1325+
path,
1326+
result,
1327+
streamUsage,
1328+
incrementalContext,
1329+
deferMap,
1330+
);
1331+
}
12321332

1333+
function completeIterableValue(
1334+
exeContext: ExecutionContext,
1335+
itemType: GraphQLOutputType,
1336+
fieldGroup: FieldGroup,
1337+
info: GraphQLResolveInfo,
1338+
path: Path,
1339+
items: Iterable<unknown>,
1340+
incrementalContext: IncrementalContext | undefined,
1341+
deferMap: ReadonlyMap<DeferUsage, DeferredFragmentRecord> | undefined,
1342+
): PromiseOrValue<ReadonlyArray<unknown>> {
1343+
let index = 0;
12331344
// This is specified as a simple map, however we're optimizing the path
12341345
// where the list contains no Promises by avoiding creating another Promise.
12351346
let containsPromise = false;
12361347
const completedResults: Array<unknown> = [];
1348+
for (const item of items) {
1349+
// No need to modify the info object containing the path,
1350+
// since from here on it is not ever accessed by resolver functions.
1351+
const itemPath = addPath(path, index, undefined);
1352+
1353+
if (
1354+
completeListItemValue(
1355+
item,
1356+
completedResults,
1357+
exeContext,
1358+
itemType,
1359+
fieldGroup,
1360+
info,
1361+
itemPath,
1362+
incrementalContext,
1363+
deferMap,
1364+
)
1365+
) {
1366+
containsPromise = true;
1367+
}
1368+
1369+
index++;
1370+
}
1371+
1372+
return containsPromise ? Promise.all(completedResults) : completedResults;
1373+
}
1374+
1375+
function completeIterableValueWithPossibleStream(
1376+
exeContext: ExecutionContext,
1377+
itemType: GraphQLOutputType,
1378+
fieldGroup: FieldGroup,
1379+
info: GraphQLResolveInfo,
1380+
path: Path,
1381+
items: Iterable<unknown>,
1382+
streamUsage: StreamUsage,
1383+
incrementalContext: IncrementalContext | undefined,
1384+
deferMap: ReadonlyMap<DeferUsage, DeferredFragmentRecord> | undefined,
1385+
): PromiseOrValue<ReadonlyArray<unknown>> {
12371386
let index = 0;
1238-
const iterator = result[Symbol.iterator]();
1387+
// This is specified as a simple map, however we're optimizing the path
1388+
// where the list contains no Promises by avoiding creating another Promise.
1389+
let containsPromise = false;
1390+
const completedResults: Array<unknown> = [];
1391+
const initialCount = streamUsage.initialCount;
1392+
const iterator = items[Symbol.iterator]();
12391393
let iteration = iterator.next();
12401394
while (!iteration.done) {
12411395
const item = iteration.value;
12421396

1243-
if (streamUsage && index >= streamUsage.initialCount) {
1397+
if (index >= initialCount) {
12441398
const streamRecord = new StreamRecord({
12451399
label: streamUsage.label,
12461400
path,

0 commit comments

Comments
 (0)