Skip to content

Commit 9a3b505

Browse files
committed
optimize some more
1 parent c396b11 commit 9a3b505

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
@@ -1088,13 +1088,68 @@ async function completeAsyncIteratorValue(
10881088
incrementalContext: IncrementalContext | undefined,
10891089
deferMap: ReadonlyMap<DeferUsage, DeferredFragmentRecord> | undefined,
10901090
): Promise<ReadonlyArray<unknown>> {
1091-
const streamUsage = getStreamUsage(exeContext, fieldGroup, path);
10921091
let containsPromise = false;
10931092
const completedResults: Array<unknown> = [];
10941093
let index = 0;
10951094
// eslint-disable-next-line no-constant-condition
10961095
while (true) {
1097-
if (streamUsage && index >= streamUsage.initialCount) {
1096+
const itemPath = addPath(path, index, undefined);
1097+
let iteration;
1098+
try {
1099+
// eslint-disable-next-line no-await-in-loop
1100+
iteration = await asyncIterator.next();
1101+
} catch (rawError) {
1102+
throw locatedError(rawError, toNodes(fieldGroup), pathToArray(path));
1103+
}
1104+
1105+
if (iteration.done) {
1106+
break;
1107+
}
1108+
1109+
if (
1110+
completeListItemValue(
1111+
iteration.value,
1112+
completedResults,
1113+
exeContext,
1114+
itemType,
1115+
fieldGroup,
1116+
info,
1117+
itemPath,
1118+
incrementalContext,
1119+
deferMap,
1120+
)
1121+
) {
1122+
containsPromise = true;
1123+
}
1124+
1125+
index++;
1126+
}
1127+
1128+
return containsPromise ? Promise.all(completedResults) : completedResults;
1129+
}
1130+
1131+
/**
1132+
* Complete a async iterator value by completing the result and calling
1133+
* recursively until all the results are completed.
1134+
*/
1135+
async function completeAsyncIteratorValueWithPossibleStream(
1136+
exeContext: ExecutionContext,
1137+
itemType: GraphQLOutputType,
1138+
fieldGroup: FieldGroup,
1139+
info: GraphQLResolveInfo,
1140+
path: Path,
1141+
asyncIterator: AsyncIterator<unknown>,
1142+
streamUsage: StreamUsage,
1143+
incrementalContext: IncrementalContext | undefined,
1144+
deferMap: ReadonlyMap<DeferUsage, DeferredFragmentRecord> | undefined,
1145+
): Promise<ReadonlyArray<unknown>> {
1146+
let containsPromise = false;
1147+
const completedResults: Array<unknown> = [];
1148+
let index = 0;
1149+
const initialCount = streamUsage.initialCount;
1150+
// eslint-disable-next-line no-constant-condition
1151+
while (true) {
1152+
if (index >= initialCount) {
10981153
const streamRecord = new StreamRecord({
10991154
label: streamUsage.label,
11001155
path,
@@ -1135,13 +1190,16 @@ async function completeAsyncIteratorValue(
11351190
try {
11361191
// eslint-disable-next-line no-await-in-loop
11371192
iteration = await asyncIterator.next();
1138-
if (iteration.done) {
1139-
break;
1140-
}
11411193
} catch (rawError) {
11421194
throw locatedError(rawError, toNodes(fieldGroup), pathToArray(path));
11431195
}
11441196

1197+
// TODO: add test case for stream returning done before initialCount
1198+
/* c8 ignore next 3 */
1199+
if (iteration.done) {
1200+
break;
1201+
}
1202+
11451203
if (
11461204
completeListItemValue(
11471205
iteration.value,
@@ -1153,14 +1211,17 @@ async function completeAsyncIteratorValue(
11531211
itemPath,
11541212
incrementalContext,
11551213
deferMap,
1156-
)
1214+
) /* c8 ignore start */
11571215
) {
1216+
// TODO: add test case for asyncIterator that yields promises
11581217
containsPromise = true;
1159-
}
1160-
index += 1;
1218+
} /* c8 ignore stop */
1219+
index++;
11611220
}
11621221

1163-
return containsPromise ? Promise.all(completedResults) : completedResults;
1222+
return containsPromise
1223+
? /* c8 ignore start */ Promise.all(completedResults)
1224+
: /* c8 ignore stop */ completedResults;
11641225
}
11651226

11661227
function addIncrementalDataRecord(
@@ -1190,17 +1251,32 @@ function completeListValue(
11901251
deferMap: ReadonlyMap<DeferUsage, DeferredFragmentRecord> | undefined,
11911252
): PromiseOrValue<ReadonlyArray<unknown>> {
11921253
const itemType = returnType.ofType;
1254+
const streamUsage = getStreamUsage(exeContext, fieldGroup, path);
11931255

11941256
if (isAsyncIterable(result)) {
11951257
const asyncIterator = result[Symbol.asyncIterator]();
11961258

1197-
return completeAsyncIteratorValue(
1259+
if (streamUsage === undefined) {
1260+
return completeAsyncIteratorValue(
1261+
exeContext,
1262+
itemType,
1263+
fieldGroup,
1264+
info,
1265+
path,
1266+
asyncIterator,
1267+
incrementalContext,
1268+
deferMap,
1269+
);
1270+
}
1271+
1272+
return completeAsyncIteratorValueWithPossibleStream(
11981273
exeContext,
11991274
itemType,
12001275
fieldGroup,
12011276
info,
12021277
path,
12031278
asyncIterator,
1279+
streamUsage,
12041280
incrementalContext,
12051281
deferMap,
12061282
);
@@ -1212,19 +1288,97 @@ function completeListValue(
12121288
);
12131289
}
12141290

1215-
const streamUsage = getStreamUsage(exeContext, fieldGroup, path);
1291+
if (streamUsage === undefined) {
1292+
return completeIterableValue(
1293+
exeContext,
1294+
itemType,
1295+
fieldGroup,
1296+
info,
1297+
path,
1298+
result,
1299+
incrementalContext,
1300+
deferMap,
1301+
);
1302+
}
1303+
1304+
return completeIterableValueWithPossibleStream(
1305+
exeContext,
1306+
itemType,
1307+
fieldGroup,
1308+
info,
1309+
path,
1310+
result,
1311+
streamUsage,
1312+
incrementalContext,
1313+
deferMap,
1314+
);
1315+
}
12161316

1317+
function completeIterableValue(
1318+
exeContext: ExecutionContext,
1319+
itemType: GraphQLOutputType,
1320+
fieldGroup: FieldGroup,
1321+
info: GraphQLResolveInfo,
1322+
path: Path,
1323+
items: Iterable<unknown>,
1324+
incrementalContext: IncrementalContext | undefined,
1325+
deferMap: ReadonlyMap<DeferUsage, DeferredFragmentRecord> | undefined,
1326+
): PromiseOrValue<ReadonlyArray<unknown>> {
1327+
let index = 0;
12171328
// This is specified as a simple map, however we're optimizing the path
12181329
// where the list contains no Promises by avoiding creating another Promise.
12191330
let containsPromise = false;
12201331
const completedResults: Array<unknown> = [];
1332+
for (const item of items) {
1333+
// No need to modify the info object containing the path,
1334+
// since from here on it is not ever accessed by resolver functions.
1335+
const itemPath = addPath(path, index, undefined);
1336+
1337+
if (
1338+
completeListItemValue(
1339+
item,
1340+
completedResults,
1341+
exeContext,
1342+
itemType,
1343+
fieldGroup,
1344+
info,
1345+
itemPath,
1346+
incrementalContext,
1347+
deferMap,
1348+
)
1349+
) {
1350+
containsPromise = true;
1351+
}
1352+
1353+
index++;
1354+
}
1355+
1356+
return containsPromise ? Promise.all(completedResults) : completedResults;
1357+
}
1358+
1359+
function completeIterableValueWithPossibleStream(
1360+
exeContext: ExecutionContext,
1361+
itemType: GraphQLOutputType,
1362+
fieldGroup: FieldGroup,
1363+
info: GraphQLResolveInfo,
1364+
path: Path,
1365+
items: Iterable<unknown>,
1366+
streamUsage: StreamUsage,
1367+
incrementalContext: IncrementalContext | undefined,
1368+
deferMap: ReadonlyMap<DeferUsage, DeferredFragmentRecord> | undefined,
1369+
): PromiseOrValue<ReadonlyArray<unknown>> {
12211370
let index = 0;
1222-
const iterator = result[Symbol.iterator]();
1371+
// This is specified as a simple map, however we're optimizing the path
1372+
// where the list contains no Promises by avoiding creating another Promise.
1373+
let containsPromise = false;
1374+
const completedResults: Array<unknown> = [];
1375+
const initialCount = streamUsage.initialCount;
1376+
const iterator = items[Symbol.iterator]();
12231377
let iteration = iterator.next();
12241378
while (!iteration.done) {
12251379
const item = iteration.value;
12261380

1227-
if (streamUsage && index >= streamUsage.initialCount) {
1381+
if (index >= initialCount) {
12281382
const streamRecord = new StreamRecord({
12291383
label: streamUsage.label,
12301384
path,

0 commit comments

Comments
 (0)