Skip to content

Commit ece5007

Browse files
committed
introduce completeValue helpers
1 parent 23a8bb9 commit ece5007

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
@@ -908,6 +908,83 @@ function completeValue(
908908
);
909909
}
910910

911+
async function completePromiseCatchingErrors(
912+
exeContext: ExecutionContext,
913+
returnType: GraphQLOutputType,
914+
fieldNodes: ReadonlyArray<FieldNode>,
915+
info: GraphQLResolveInfo,
916+
path: Path,
917+
result: Promise<unknown>,
918+
asyncPayloadRecord?: AsyncPayloadRecord,
919+
): Promise<unknown> {
920+
try {
921+
const resolved = await result;
922+
let completed = completeValue(
923+
exeContext,
924+
returnType,
925+
fieldNodes,
926+
info,
927+
path,
928+
resolved,
929+
asyncPayloadRecord,
930+
);
931+
if (isPromise(completed)) {
932+
// see: https://github.com/tc39/proposal-faster-promise-adoption
933+
// it is faster to await a promise prior to returning it from an async function
934+
completed = await completed;
935+
}
936+
return completed;
937+
} catch (rawError) {
938+
const errors = asyncPayloadRecord?.errors ?? exeContext.errors;
939+
const error = locatedError(rawError, fieldNodes, pathToArray(path));
940+
const handledError = handleFieldError(error, returnType, errors);
941+
filterSubsequentPayloads(exeContext, path, asyncPayloadRecord);
942+
return handledError;
943+
}
944+
}
945+
946+
function completeValueCatchingErrors(
947+
exeContext: ExecutionContext,
948+
returnType: GraphQLOutputType,
949+
fieldNodes: ReadonlyArray<FieldNode>,
950+
info: GraphQLResolveInfo,
951+
path: Path,
952+
result: unknown,
953+
asyncPayloadRecord?: AsyncPayloadRecord,
954+
): PromiseOrValue<unknown> {
955+
let completedValue: PromiseOrValue<unknown>;
956+
try {
957+
completedValue = completeValue(
958+
exeContext,
959+
returnType,
960+
fieldNodes,
961+
info,
962+
path,
963+
result,
964+
asyncPayloadRecord,
965+
);
966+
} catch (rawError) {
967+
const errors = asyncPayloadRecord?.errors ?? exeContext.errors;
968+
const error = locatedError(rawError, fieldNodes, pathToArray(path));
969+
const handledError = handleFieldError(error, returnType, errors);
970+
filterSubsequentPayloads(exeContext, path, asyncPayloadRecord);
971+
return handledError;
972+
}
973+
974+
if (isPromise(completedValue)) {
975+
// Note: we don't rely on a `catch` method, but we do expect "thenable"
976+
// to take a second callback for the error case.
977+
completedValue = completedValue.then(undefined, (rawError) => {
978+
const errors = asyncPayloadRecord?.errors ?? exeContext.errors;
979+
const error = locatedError(rawError, fieldNodes, pathToArray(path));
980+
const handledError = handleFieldError(error, returnType, errors);
981+
filterSubsequentPayloads(exeContext, path, asyncPayloadRecord);
982+
return handledError;
983+
});
984+
}
985+
return completedValue;
986+
}
987+
911988
/**
912989
* Returns an object containing the `@stream` arguments if a field should be
913990
* streamed based on the experimental flag, stream directive present and
@@ -1925,69 +2002,17 @@ function executeStreamIteratorItem(
19252002
exeContext,
19262003
streamContext,
19272004
});
1928-
let completedItem: PromiseOrValue<unknown>;
1929-
try {
1930-
try {
1931-
if (isPromise(item)) {
1932-
completedItem = item.then((resolved) =>
1933-
completeValue(
1934-
exeContext,
1935-
itemType,
1936-
fieldNodes,
1937-
info,
1938-
itemPath,
1939-
resolved,
1940-
asyncPayloadRecord,
1941-
),
1942-
);
1943-
} else {
1944-
completedItem = completeValue(
1945-
exeContext,
1946-
itemType,
1947-
fieldNodes,
1948-
info,
1949-
itemPath,
1950-
item,
1951-
asyncPayloadRecord,
1952-
);
1953-
}
1954-
1955-
if (isPromise(completedItem)) {
1956-
// Note: we don't rely on a `catch` method, but we do expect "thenable"
1957-
// to take a second callback for the error case.
1958-
completedItem = completedItem.then(undefined, (rawError) => {
1959-
const error = locatedError(
1960-
rawError,
1961-
fieldNodes,
1962-
pathToArray(itemPath),
1963-
);
1964-
const handledError = handleFieldError(
1965-
error,
1966-
itemType,
1967-
asyncPayloadRecord.errors,
1968-
);
1969-
filterSubsequentPayloads(exeContext, itemPath, asyncPayloadRecord);
1970-
return handledError;
1971-
});
1972-
}
1973-
} catch (rawError) {
1974-
const error = locatedError(rawError, fieldNodes, pathToArray(itemPath));
1975-
completedItem = handleFieldError(
1976-
error,
1977-
itemType,
1978-
asyncPayloadRecord.errors,
1979-
);
1980-
filterSubsequentPayloads(exeContext, itemPath, asyncPayloadRecord);
1981-
}
1982-
} catch (error) {
1983-
asyncPayloadRecord.errors.push(error);
1984-
filterSubsequentPayloads(exeContext, path, asyncPayloadRecord);
1985-
asyncPayloadRecord.addItems(null);
1986-
return asyncPayloadRecord;
1987-
}
1988-
19892005
let completedItems: PromiseOrValue<Array<unknown> | null>;
1990-
if (isPromise(completedItem)) {
2006+
if (isPromise(item)) {
2007+
const completedItem = completePromiseCatchingErrors(
2008+
exeContext,
2009+
itemType,
2010+
fieldNodes,
2011+
info,
2012+
itemPath,
2013+
item,
2014+
asyncPayloadRecord,
2015+
);
19912016
completedItems = completedItem.then(
19922017
(value) => [value],
19932018
(error) => {
@@ -1997,6 +2022,23 @@ function executeStreamIteratorItem(
19972022
},
19982023
);
19992024
} else {
2025+
let completedItem;
2026+
try {
2027+
completedItem = completeValueCatchingErrors(
2028+
exeContext,
2029+
itemType,
2030+
fieldNodes,
2031+
info,
2032+
itemPath,
2033+
item,
2034+
asyncPayloadRecord,
2035+
);
2036+
} catch (error) {
2037+
asyncPayloadRecord.errors.push(error);
2038+
filterSubsequentPayloads(exeContext, path, asyncPayloadRecord);
2039+
asyncPayloadRecord.addItems(null);
2040+
return asyncPayloadRecord;
2041+
}
20002042
completedItems = [completedItem];
20012043
}
20022044

@@ -2027,37 +2069,18 @@ async function executeStreamAsyncIteratorItem(
20272069
// don't continue if iterator throws
20282070
return { done: true, value };
20292071
}
2030-
let completedItem;
2031-
try {
2032-
completedItem = completeValue(
2072+
return {
2073+
done: false,
2074+
value: completeValueCatchingErrors(
20332075
exeContext,
20342076
itemType,
20352077
fieldNodes,
20362078
info,
20372079
itemPath,
20382080
item,
20392081
asyncPayloadRecord,
2040-
);
2041-
2042-
if (isPromise(completedItem)) {
2043-
completedItem = completedItem.then(undefined, (rawError) => {
2044-
const error = locatedError(rawError, fieldNodes, pathToArray(itemPath));
2045-
const handledError = handleFieldError(
2046-
error,
2047-
itemType,
2048-
asyncPayloadRecord.errors,
2049-
);
2050-
filterSubsequentPayloads(exeContext, itemPath, asyncPayloadRecord);
2051-
return handledError;
2052-
});
2053-
}
2054-
return { done: false, value: completedItem };
2055-
} catch (rawError) {
2056-
const error = locatedError(rawError, fieldNodes, pathToArray(itemPath));
2057-
const value = handleFieldError(error, itemType, asyncPayloadRecord.errors);
2058-
filterSubsequentPayloads(exeContext, itemPath, asyncPayloadRecord);
2059-
return { done: false, value };
2060-
}
2082+
),
2083+
};
20612084
}
20622085

20632086
async function executeStreamAsyncIterator(

0 commit comments

Comments
 (0)