Skip to content

Commit 68841c2

Browse files
committed
introduce completeValue helpers within stream execution
Motivation: = code reuse = helpers are optimized to decrease the number of ticks. This results in changes to order in promise resolution with changes to the value of hasNext.
1 parent b020207 commit 68841c2

File tree

2 files changed

+109
-91
lines changed

2 files changed

+109
-91
lines changed

src/execution/__tests__/stream-test.ts

Lines changed: 0 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -483,11 +483,6 @@ describe('Execute: stream directive', () => {
483483
},
484484
],
485485
},
486-
],
487-
hasNext: true,
488-
},
489-
{
490-
incremental: [
491486
{
492487
items: [{ name: 'Leia', id: '3' }],
493488
path: ['friendList', 2],

src/execution/execute.ts

Lines changed: 109 additions & 86 deletions
Original file line numberDiff line numberDiff line change
@@ -905,6 +905,83 @@ function completeValue(
905905
);
906906
}
907907

908+
async function completePromiseCatchingErrors(
909+
exeContext: ExecutionContext,
910+
returnType: GraphQLOutputType,
911+
fieldNodes: ReadonlyArray<FieldNode>,
912+
info: GraphQLResolveInfo,
913+
path: Path,
914+
result: Promise<unknown>,
915+
asyncPayloadRecord?: AsyncPayloadRecord,
916+
): Promise<unknown> {
917+
try {
918+
const resolved = await result;
919+
let completed = completeValue(
920+
exeContext,
921+
returnType,
922+
fieldNodes,
923+
info,
924+
path,
925+
resolved,
926+
asyncPayloadRecord,
927+
);
928+
if (isPromise(completed)) {
929+
// see: https://github.com/tc39/proposal-faster-promise-adoption
930+
// it is faster to await a promise prior to returning it from an async function
931+
completed = await completed;
932+
}
933+
return completed;
934+
} catch (rawError) {
935+
const errors = asyncPayloadRecord?.errors ?? exeContext.errors;
936+
const error = locatedError(rawError, fieldNodes, pathToArray(path));
937+
const handledError = handleFieldError(error, returnType, errors);
938+
filterSubsequentPayloads(exeContext, path, asyncPayloadRecord);
939+
return handledError;
940+
}
941+
}
942+
943+
function completeValueCatchingErrors(
944+
exeContext: ExecutionContext,
945+
returnType: GraphQLOutputType,
946+
fieldNodes: ReadonlyArray<FieldNode>,
947+
info: GraphQLResolveInfo,
948+
path: Path,
949+
result: unknown,
950+
asyncPayloadRecord?: AsyncPayloadRecord,
951+
): PromiseOrValue<unknown> {
952+
let completedValue: PromiseOrValue<unknown>;
953+
try {
954+
completedValue = completeValue(
955+
exeContext,
956+
returnType,
957+
fieldNodes,
958+
info,
959+
path,
960+
result,
961+
asyncPayloadRecord,
962+
);
963+
} catch (rawError) {
964+
const errors = asyncPayloadRecord?.errors ?? exeContext.errors;
965+
const error = locatedError(rawError, fieldNodes, pathToArray(path));
966+
const handledError = handleFieldError(error, returnType, errors);
967+
filterSubsequentPayloads(exeContext, path, asyncPayloadRecord);
968+
return handledError;
969+
}
970+
971+
if (isPromise(completedValue)) {
972+
// Note: we don't rely on a `catch` method, but we do expect "thenable"
973+
// to take a second callback for the error case.
974+
completedValue = completedValue.then(undefined, (rawError) => {
975+
const errors = asyncPayloadRecord?.errors ?? exeContext.errors;
976+
const error = locatedError(rawError, fieldNodes, pathToArray(path));
977+
const handledError = handleFieldError(error, returnType, errors);
978+
filterSubsequentPayloads(exeContext, path, asyncPayloadRecord);
979+
return handledError;
980+
});
981+
}
982+
return completedValue;
983+
}
984+
908985
/**
909986
* Returns an object containing the `@stream` arguments if a field should be
910987
* streamed based on the experimental flag, stream directive present and
@@ -1867,69 +1944,17 @@ function executeStreamField(
18671944
parentContext,
18681945
exeContext,
18691946
});
1870-
let completedItem: PromiseOrValue<unknown>;
1871-
try {
1872-
try {
1873-
if (isPromise(item)) {
1874-
completedItem = item.then((resolved) =>
1875-
completeValue(
1876-
exeContext,
1877-
itemType,
1878-
fieldNodes,
1879-
info,
1880-
itemPath,
1881-
resolved,
1882-
asyncPayloadRecord,
1883-
),
1884-
);
1885-
} else {
1886-
completedItem = completeValue(
1887-
exeContext,
1888-
itemType,
1889-
fieldNodes,
1890-
info,
1891-
itemPath,
1892-
item,
1893-
asyncPayloadRecord,
1894-
);
1895-
}
1896-
1897-
if (isPromise(completedItem)) {
1898-
// Note: we don't rely on a `catch` method, but we do expect "thenable"
1899-
// to take a second callback for the error case.
1900-
completedItem = completedItem.then(undefined, (rawError) => {
1901-
const error = locatedError(
1902-
rawError,
1903-
fieldNodes,
1904-
pathToArray(itemPath),
1905-
);
1906-
const handledError = handleFieldError(
1907-
error,
1908-
itemType,
1909-
asyncPayloadRecord.errors,
1910-
);
1911-
filterSubsequentPayloads(exeContext, itemPath, asyncPayloadRecord);
1912-
return handledError;
1913-
});
1914-
}
1915-
} catch (rawError) {
1916-
const error = locatedError(rawError, fieldNodes, pathToArray(itemPath));
1917-
completedItem = handleFieldError(
1918-
error,
1919-
itemType,
1920-
asyncPayloadRecord.errors,
1921-
);
1922-
filterSubsequentPayloads(exeContext, itemPath, asyncPayloadRecord);
1923-
}
1924-
} catch (error) {
1925-
asyncPayloadRecord.errors.push(error);
1926-
filterSubsequentPayloads(exeContext, path, asyncPayloadRecord);
1927-
asyncPayloadRecord.addItems(null);
1928-
return asyncPayloadRecord;
1929-
}
1930-
19311947
let completedItems: PromiseOrValue<Array<unknown> | null>;
1932-
if (isPromise(completedItem)) {
1948+
if (isPromise(item)) {
1949+
const completedItem = completePromiseCatchingErrors(
1950+
exeContext,
1951+
itemType,
1952+
fieldNodes,
1953+
info,
1954+
itemPath,
1955+
item,
1956+
asyncPayloadRecord,
1957+
);
19331958
completedItems = completedItem.then(
19341959
(value) => [value],
19351960
(error) => {
@@ -1939,6 +1964,23 @@ function executeStreamField(
19391964
},
19401965
);
19411966
} else {
1967+
let completedItem;
1968+
try {
1969+
completedItem = completeValueCatchingErrors(
1970+
exeContext,
1971+
itemType,
1972+
fieldNodes,
1973+
info,
1974+
itemPath,
1975+
item,
1976+
asyncPayloadRecord,
1977+
);
1978+
} catch (error) {
1979+
asyncPayloadRecord.errors.push(error);
1980+
filterSubsequentPayloads(exeContext, path, asyncPayloadRecord);
1981+
asyncPayloadRecord.addItems(null);
1982+
return asyncPayloadRecord;
1983+
}
19421984
completedItems = [completedItem];
19431985
}
19441986

@@ -1969,37 +2011,18 @@ async function executeStreamIteratorItem(
19692011
// don't continue if iterator throws
19702012
return { done: true, value };
19712013
}
1972-
let completedItem;
1973-
try {
1974-
completedItem = completeValue(
2014+
return {
2015+
done: false,
2016+
value: completeValueCatchingErrors(
19752017
exeContext,
19762018
itemType,
19772019
fieldNodes,
19782020
info,
19792021
itemPath,
19802022
item,
19812023
asyncPayloadRecord,
1982-
);
1983-
1984-
if (isPromise(completedItem)) {
1985-
completedItem = completedItem.then(undefined, (rawError) => {
1986-
const error = locatedError(rawError, fieldNodes, pathToArray(itemPath));
1987-
const handledError = handleFieldError(
1988-
error,
1989-
itemType,
1990-
asyncPayloadRecord.errors,
1991-
);
1992-
filterSubsequentPayloads(exeContext, itemPath, asyncPayloadRecord);
1993-
return handledError;
1994-
});
1995-
}
1996-
return { done: false, value: completedItem };
1997-
} catch (rawError) {
1998-
const error = locatedError(rawError, fieldNodes, pathToArray(itemPath));
1999-
const value = handleFieldError(error, itemType, asyncPayloadRecord.errors);
2000-
filterSubsequentPayloads(exeContext, itemPath, asyncPayloadRecord);
2001-
return { done: false, value };
2002-
}
2024+
),
2025+
};
20032026
}
20042027

20052028
async function executeStreamIterator(

0 commit comments

Comments
 (0)