Skip to content

Commit a4a2178

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 40ff40a commit a4a2178

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
@@ -2025,43 +2025,52 @@ async function executeStreamIterator(
20252025
exeContext,
20262026
});
20272027

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

0 commit comments

Comments
 (0)