Skip to content

Commit 52ff552

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 a4a2178 commit 52ff552

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
@@ -904,6 +904,83 @@ function completeValue(
904904
);
905905
}
906906

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

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

20042027
async function executeStreamIterator(

0 commit comments

Comments
 (0)