Skip to content

Commit

Permalink
perf: check if streamUsage is defined outside the loop
Browse files Browse the repository at this point in the history
introducing separate looping functions for when streamUsage is defined
  • Loading branch information
yaacovCR committed Apr 7, 2024
1 parent 9563577 commit 63e8957
Showing 1 changed file with 173 additions and 7 deletions.
180 changes: 173 additions & 7 deletions src/execution/execute.ts
Expand Up @@ -1014,7 +1014,6 @@ function getStreamUsage(

return streamUsage;
}

/**
* Complete a async iterator value by completing the result and calling
* recursively until all the results are completed.
Expand All @@ -1033,10 +1032,87 @@ async function completeAsyncIteratorValue(
const completedResults: Array<unknown> = [];
const acc: GraphQLResult<Array<unknown>> = [completedResults, []];
let index = 0;
const streamUsage = getStreamUsage(exeContext, fieldGroup, path);
// eslint-disable-next-line no-constant-condition
while (true) {
if (streamUsage && index >= streamUsage.initialCount) {
const itemPath = addPath(path, index, undefined);
let iteration;
try {
// eslint-disable-next-line no-await-in-loop
iteration = await asyncIterator.next();
} catch (rawError) {
throw locatedError(rawError, toNodes(fieldGroup), pathToArray(path));
}

if (iteration.done) {
break;
}

const item = iteration.value;
// TODO: add test case for asyncIterator returning a promise
/* c8 ignore start */
if (isPromise(item)) {
completedResults.push(
completePromisedListItemValue(
item,
acc,
exeContext,
itemType,
fieldGroup,
info,
itemPath,
incrementalContext,
deferMap,
),
);
containsPromise = true;
} else if (
/* c8 ignore stop */
completeListItemValue(
item,
completedResults,
acc,
exeContext,
itemType,
fieldGroup,
info,
itemPath,
incrementalContext,
deferMap,
)
) {
containsPromise = true;
}
index++;
}

return containsPromise
? Promise.all(completedResults).then((resolved) => [resolved, acc[1]])
: acc;
}

/**
* Complete a async iterator value by completing the result and calling
* recursively until all the results are completed.
*/
async function completeAsyncIteratorValueWithPossibleStream(
exeContext: ExecutionContext,
itemType: GraphQLOutputType,
fieldGroup: FieldGroup,
info: GraphQLResolveInfo,
path: Path,
asyncIterator: AsyncIterator<unknown>,
streamUsage: StreamUsage,
incrementalContext: IncrementalContext | undefined,
deferMap: ReadonlyMap<DeferUsage, DeferredFragmentRecord> | undefined,
): Promise<GraphQLResult<ReadonlyArray<unknown>>> {
let containsPromise = false;
const completedResults: Array<unknown> = [];
const acc: GraphQLResult<Array<unknown>> = [completedResults, []];
let index = 0;
const initialCount = streamUsage.initialCount;
// eslint-disable-next-line no-constant-condition
while (true) {
if (index >= initialCount) {
const streamRecord = new StreamRecord({
label: streamUsage.label,
path,
Expand Down Expand Up @@ -1147,17 +1223,32 @@ function completeListValue(
deferMap: ReadonlyMap<DeferUsage, DeferredFragmentRecord> | undefined,
): PromiseOrValue<GraphQLResult<ReadonlyArray<unknown>>> {
const itemType = returnType.ofType;
const streamUsage = getStreamUsage(exeContext, fieldGroup, path);

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

return completeAsyncIteratorValue(
if (streamUsage === undefined) {
return completeAsyncIteratorValue(
exeContext,
itemType,
fieldGroup,
info,
path,
asyncIterator,
incrementalContext,
deferMap,
);
}

return completeAsyncIteratorValueWithPossibleStream(
exeContext,
itemType,
fieldGroup,
info,
path,
asyncIterator,
streamUsage,
incrementalContext,
deferMap,
);
Expand All @@ -1169,13 +1260,27 @@ function completeListValue(
);
}

return completeIterableValue(
if (streamUsage === undefined) {
return completeIterableValue(
exeContext,
itemType,
fieldGroup,
info,
path,
result,
incrementalContext,
deferMap,
);
}

return completeIterableValueWithPossibleStream(
exeContext,
itemType,
fieldGroup,
info,
path,
result,
streamUsage,
incrementalContext,
deferMap,
);
Expand All @@ -1197,13 +1302,74 @@ function completeIterableValue(
const completedResults: Array<unknown> = [];
const acc: GraphQLResult<Array<unknown>> = [completedResults, []];
let index = 0;
const streamUsage = getStreamUsage(exeContext, fieldGroup, path);
for (const item of items) {
// No need to modify the info object containing the path,
// since from here on it is not ever accessed by resolver functions.
const itemPath = addPath(path, index, undefined);

if (isPromise(item)) {
completedResults.push(
completePromisedListItemValue(
item,
acc,
exeContext,
itemType,
fieldGroup,
info,
itemPath,
incrementalContext,
deferMap,
),
);
containsPromise = true;
} else if (
completeListItemValue(
item,
completedResults,
acc,
exeContext,
itemType,
fieldGroup,
info,
itemPath,
incrementalContext,
deferMap,
)
) {
containsPromise = true;
}
index++;
}

return containsPromise
? Promise.all(completedResults).then((resolved) => [resolved, acc[1]])
: acc;
}

function completeIterableValueWithPossibleStream(
exeContext: ExecutionContext,
itemType: GraphQLOutputType,
fieldGroup: FieldGroup,
info: GraphQLResolveInfo,
path: Path,
items: Iterable<unknown>,
streamUsage: StreamUsage,
incrementalContext: IncrementalContext | undefined,
deferMap: ReadonlyMap<DeferUsage, DeferredFragmentRecord> | undefined,
): PromiseOrValue<GraphQLResult<ReadonlyArray<unknown>>> {
// This is specified as a simple map, however we're optimizing the path
// where the list contains no Promises by avoiding creating another Promise.
let containsPromise = false;
const completedResults: Array<unknown> = [];
const acc: GraphQLResult<Array<unknown>> = [completedResults, []];
let index = 0;
const initialCount = streamUsage.initialCount;
const iterator = items[Symbol.iterator]();
let iteration = iterator.next();
while (!iteration.done) {
const item = iteration.value;

if (streamUsage && index >= streamUsage.initialCount) {
if (index >= initialCount) {
const streamRecord = new StreamRecord({
label: streamUsage.label,
path,
Expand Down

0 comments on commit 63e8957

Please sign in to comment.