Skip to content

Commit 5e7b220

Browse files
committed
remove extra then from executeStreamIterator
This results in changes to promise resolution such that there are changes in the value of hasNext.
1 parent 90774d7 commit 5e7b220

File tree

2 files changed

+146
-47
lines changed

2 files changed

+146
-47
lines changed

src/execution/__tests__/stream-test.ts

Lines changed: 110 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
import { assert } from 'chai';
1+
import { assert, expect } from 'chai';
22
import { describe, it } from 'mocha';
33

44
import { expectJSON } from '../../__testUtils__/expectJSON.js';
@@ -851,6 +851,57 @@ describe('Execute: stream directive', () => {
851851
]);
852852
});
853853
it('Handles async errors thrown by completeValue after initialCount is reached', async () => {
854+
const document = parse(`
855+
query {
856+
friendList @stream(initialCount: 1) {
857+
nonNullName
858+
}
859+
}
860+
`);
861+
const result = await complete(document, {
862+
friendList: () => [
863+
Promise.resolve({ nonNullName: friends[0].name }),
864+
Promise.resolve({
865+
nonNullName: () => Promise.reject(new Error('Oops')),
866+
}),
867+
Promise.resolve({ nonNullName: friends[1].name }),
868+
],
869+
});
870+
expectJSON(result).toDeepEqual([
871+
{
872+
data: {
873+
friendList: [{ nonNullName: 'Luke' }],
874+
},
875+
hasNext: true,
876+
},
877+
{
878+
incremental: [
879+
{
880+
items: [null],
881+
path: ['friendList', 1],
882+
errors: [
883+
{
884+
message: 'Oops',
885+
locations: [{ line: 4, column: 11 }],
886+
path: ['friendList', 1, 'nonNullName'],
887+
},
888+
],
889+
},
890+
],
891+
hasNext: true,
892+
},
893+
{
894+
incremental: [
895+
{
896+
items: [{ nonNullName: 'Han' }],
897+
path: ['friendList', 2],
898+
},
899+
],
900+
hasNext: false,
901+
},
902+
]);
903+
});
904+
it('Handles async errors thrown by completeValue after initialCount is reached for a non-nullable list', async () => {
854905
const document = parse(`
855906
query {
856907
nonNullFriendList @stream(initialCount: 1) {
@@ -946,6 +997,50 @@ describe('Execute: stream directive', () => {
946997
},
947998
]);
948999
});
1000+
it('Handles async errors thrown by completeValue after initialCount is reached from async iterable for a non-nullable list', async () => {
1001+
const document = parse(`
1002+
query {
1003+
nonNullFriendList @stream(initialCount: 1) {
1004+
nonNullName
1005+
}
1006+
}
1007+
`);
1008+
const result = await complete(document, {
1009+
async *nonNullFriendList() {
1010+
yield await Promise.resolve({ nonNullName: friends[0].name });
1011+
yield await Promise.resolve({
1012+
nonNullName: () => Promise.reject(new Error('Oops')),
1013+
});
1014+
yield await Promise.resolve({
1015+
nonNullName: friends[1].name,
1016+
}); /* c8 ignore start */
1017+
} /* c8 ignore stop */,
1018+
});
1019+
expectJSON(result).toDeepEqual([
1020+
{
1021+
data: {
1022+
nonNullFriendList: [{ nonNullName: 'Luke' }],
1023+
},
1024+
hasNext: true,
1025+
},
1026+
{
1027+
incremental: [
1028+
{
1029+
items: null,
1030+
path: ['nonNullFriendList', 1],
1031+
errors: [
1032+
{
1033+
message: 'Oops',
1034+
locations: [{ line: 4, column: 11 }],
1035+
path: ['nonNullFriendList', 1, 'nonNullName'],
1036+
},
1037+
],
1038+
},
1039+
],
1040+
hasNext: false,
1041+
},
1042+
]);
1043+
});
9491044
it('Filters payloads that are nulled', async () => {
9501045
const document = parse(`
9511046
query {
@@ -961,8 +1056,8 @@ describe('Execute: stream directive', () => {
9611056
nestedObject: {
9621057
nonNullScalarField: () => Promise.resolve(null),
9631058
async *nestedFriendList() {
964-
yield await Promise.resolve(friends[0]);
965-
},
1059+
yield await Promise.resolve(friends[0]); /* c8 ignore start */
1060+
} /* c8 ignore stop */,
9661061
},
9671062
});
9681063
expectJSON(result).toDeepEqual({
@@ -1061,9 +1156,6 @@ describe('Execute: stream directive', () => {
10611156
path: ['nestedObject', 'nestedFriendList', 0],
10621157
},
10631158
],
1064-
hasNext: true,
1065-
},
1066-
{
10671159
hasNext: false,
10681160
},
10691161
]);
@@ -1088,8 +1180,8 @@ describe('Execute: stream directive', () => {
10881180
deeperNestedObject: {
10891181
nonNullScalarField: () => Promise.resolve(null),
10901182
async *deeperNestedFriendList() {
1091-
yield await Promise.resolve(friends[0]);
1092-
},
1183+
yield await Promise.resolve(friends[0]); /* c8 ignore start */
1184+
} /* c8 ignore stop */,
10931185
},
10941186
},
10951187
});
@@ -1176,14 +1268,17 @@ describe('Execute: stream directive', () => {
11761268

11771269
it('Returns iterator and ignores errors when stream payloads are filtered', async () => {
11781270
let returned = false;
1179-
let index = 0;
1271+
let requested = false;
11801272
const iterable = {
11811273
[Symbol.asyncIterator]: () => ({
11821274
next: () => {
1183-
const friend = friends[index++];
1184-
if (!friend) {
1185-
return Promise.resolve({ done: true, value: undefined });
1275+
if (requested) {
1276+
/* c8 ignore next 3 */
1277+
// Not reached, iterator should end immediately.
1278+
expect.fail('Not reached');
11861279
}
1280+
requested = true;
1281+
const friend = friends[0];
11871282
return Promise.resolve({
11881283
done: false,
11891284
value: {
@@ -1261,17 +1356,12 @@ describe('Execute: stream directive', () => {
12611356
],
12621357
},
12631358
],
1264-
hasNext: true,
1359+
hasNext: false,
12651360
},
12661361
});
1267-
const result3 = await iterator.next();
1268-
expectJSON(result3).toDeepEqual({
1269-
done: false,
1270-
value: { hasNext: false },
1271-
});
12721362

1273-
const result4 = await iterator.next();
1274-
expectJSON(result4).toDeepEqual({ done: true, value: undefined });
1363+
const result3 = await iterator.next();
1364+
expectJSON(result3).toDeepEqual({ done: true, value: undefined });
12751365

12761366
assert(returned);
12771367
});

src/execution/execute.ts

Lines changed: 36 additions & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -2026,43 +2026,52 @@ async function executeStreamIterator(
20262026
exeContext,
20272027
});
20282028

2029-
const dataPromise = executeStreamIteratorItem(
2030-
iterator,
2031-
exeContext,
2032-
fieldNodes,
2033-
info,
2034-
itemType,
2035-
asyncPayloadRecord,
2036-
itemPath,
2037-
);
2038-
2039-
asyncPayloadRecord.addItems(
2040-
dataPromise
2041-
.then(({ value }) => value)
2042-
.then(
2043-
(value) => [value],
2044-
(err) => {
2045-
asyncPayloadRecord.errors.push(err);
2046-
return null;
2047-
},
2048-
),
2049-
);
2029+
let iteration;
20502030
try {
20512031
// eslint-disable-next-line no-await-in-loop
2052-
const { done } = await dataPromise;
2053-
if (done) {
2054-
break;
2055-
}
2056-
} catch (err) {
2057-
// entire stream has errored and bubbled upwards
2032+
iteration = await executeStreamIteratorItem(
2033+
iterator,
2034+
exeContext,
2035+
fieldNodes,
2036+
info,
2037+
itemType,
2038+
asyncPayloadRecord,
2039+
itemPath,
2040+
);
2041+
} catch (error) {
2042+
asyncPayloadRecord.errors.push(error);
20582043
filterSubsequentPayloads(exeContext, path, asyncPayloadRecord);
2044+
asyncPayloadRecord.addItems(null);
2045+
// entire stream has errored and bubbled upwards
20592046
if (iterator?.return) {
20602047
iterator.return().catch(() => {
20612048
// ignore errors
20622049
});
20632050
}
20642051
return;
20652052
}
2053+
2054+
const { done, value: completedItem } = iteration;
2055+
2056+
let completedItems: PromiseOrValue<Array<unknown> | null>;
2057+
if (isPromise(completedItem)) {
2058+
completedItems = completedItem.then(
2059+
(value) => [value],
2060+
(error) => {
2061+
asyncPayloadRecord.errors.push(error);
2062+
filterSubsequentPayloads(exeContext, path, asyncPayloadRecord);
2063+
return null;
2064+
},
2065+
);
2066+
} else {
2067+
completedItems = [completedItem];
2068+
}
2069+
2070+
asyncPayloadRecord.addItems(completedItems);
2071+
2072+
if (done) {
2073+
break;
2074+
}
20662075
previousAsyncPayloadRecord = asyncPayloadRecord;
20672076
index++;
20682077
}

0 commit comments

Comments
 (0)