@@ -2,6 +2,7 @@ import { describe, it } from 'mocha';
22
33import { expectJSON } from '../../__testUtils__/expectJSON' ;
44
5+ import { invariant } from '../../jsutils/invariant' ;
56import { isAsyncIterable } from '../../jsutils/isAsyncIterable' ;
67
78import type { DocumentNode } from '../../language/ast' ;
@@ -111,6 +112,37 @@ const query = new GraphQLObjectType({
111112 yield await Promise . resolve ( { } ) ;
112113 } ,
113114 } ,
115+ asyncIterableListDelayed : {
116+ type : new GraphQLList ( friendType ) ,
117+ async * resolve ( ) {
118+ for ( const friend of friends ) {
119+ // pause an additional ms before yielding to allow time
120+ // for tests to return or throw before next value is processed.
121+ // eslint-disable-next-line no-await-in-loop
122+ await new Promise ( ( r ) => setTimeout ( r , 1 ) ) ;
123+ yield friend ; /* c8 ignore start */
124+ // Not reachable, early return
125+ }
126+ } /* c8 ignore stop */ ,
127+ } ,
128+ asyncIterableListNoReturn : {
129+ type : new GraphQLList ( friendType ) ,
130+ resolve ( ) {
131+ let i = 0 ;
132+ return {
133+ [ Symbol . asyncIterator ] : ( ) => ( {
134+ async next ( ) {
135+ const friend = friends [ i ++ ] ;
136+ if ( friend ) {
137+ await new Promise ( ( r ) => setTimeout ( r , 1 ) ) ;
138+ return { value : friend , done : false } ;
139+ }
140+ return { value : undefined , done : true } ;
141+ } ,
142+ } ) ,
143+ } ;
144+ } ,
145+ } ,
114146 asyncIterableListDelayedClose : {
115147 type : new GraphQLList ( friendType ) ,
116148 async * resolve ( ) {
@@ -1011,4 +1043,175 @@ describe('Execute: stream directive', () => {
10111043 } ,
10121044 ] ) ;
10131045 } ) ;
1046+ it ( 'Returns underlying async iterables when dispatcher is returned' , async ( ) => {
1047+ const document = parse ( `
1048+ query {
1049+ asyncIterableListDelayed @stream(initialCount: 1) {
1050+ name
1051+ id
1052+ }
1053+ }
1054+ ` ) ;
1055+ const schema = new GraphQLSchema ( { query } ) ;
1056+
1057+ const executeResult = await execute ( { schema, document, rootValue : { } } ) ;
1058+ invariant ( isAsyncIterable ( executeResult ) ) ;
1059+ const iterator = executeResult [ Symbol . asyncIterator ] ( ) ;
1060+
1061+ const result1 = await iterator . next ( ) ;
1062+ expectJSON ( result1 ) . toDeepEqual ( {
1063+ done : false ,
1064+ value : {
1065+ data : {
1066+ asyncIterableListDelayed : [
1067+ {
1068+ id : '1' ,
1069+ name : 'Luke' ,
1070+ } ,
1071+ ] ,
1072+ } ,
1073+ hasNext : true ,
1074+ } ,
1075+ } ) ;
1076+
1077+ const returnPromise = iterator . return ( ) ;
1078+
1079+ // this result had started processing before return was called
1080+ const result2 = await iterator . next ( ) ;
1081+ expectJSON ( result2 ) . toDeepEqual ( {
1082+ done : false ,
1083+ value : {
1084+ data : {
1085+ id : '2' ,
1086+ name : 'Han' ,
1087+ } ,
1088+ hasNext : true ,
1089+ path : [ 'asyncIterableListDelayed' , 1 ] ,
1090+ } ,
1091+ } ) ;
1092+
1093+ // third result is not returned because async iterator has returned
1094+ const result3 = await iterator . next ( ) ;
1095+ expectJSON ( result3 ) . toDeepEqual ( {
1096+ done : true ,
1097+ value : undefined ,
1098+ } ) ;
1099+ await returnPromise ;
1100+ } ) ;
1101+ it ( 'Can return async iterable when underlying iterable does not have a return method' , async ( ) => {
1102+ const document = parse ( `
1103+ query {
1104+ asyncIterableListNoReturn @stream(initialCount: 1) {
1105+ name
1106+ id
1107+ }
1108+ }
1109+ ` ) ;
1110+ const schema = new GraphQLSchema ( { query } ) ;
1111+
1112+ const executeResult = await execute ( { schema, document, rootValue : { } } ) ;
1113+ invariant ( isAsyncIterable ( executeResult ) ) ;
1114+ const iterator = executeResult [ Symbol . asyncIterator ] ( ) ;
1115+
1116+ const result1 = await iterator . next ( ) ;
1117+ expectJSON ( result1 ) . toDeepEqual ( {
1118+ done : false ,
1119+ value : {
1120+ data : {
1121+ asyncIterableListNoReturn : [
1122+ {
1123+ id : '1' ,
1124+ name : 'Luke' ,
1125+ } ,
1126+ ] ,
1127+ } ,
1128+ hasNext : true ,
1129+ } ,
1130+ } ) ;
1131+
1132+ const returnPromise = iterator . return ( ) ;
1133+
1134+ // this result had started processing before return was called
1135+ const result2 = await iterator . next ( ) ;
1136+ expectJSON ( result2 ) . toDeepEqual ( {
1137+ done : false ,
1138+ value : {
1139+ data : {
1140+ id : '2' ,
1141+ name : 'Han' ,
1142+ } ,
1143+ hasNext : true ,
1144+ path : [ 'asyncIterableListNoReturn' , 1 ] ,
1145+ } ,
1146+ } ) ;
1147+
1148+ // third result is not returned because async iterator has returned
1149+ const result3 = await iterator . next ( ) ;
1150+ expectJSON ( result3 ) . toDeepEqual ( {
1151+ done : true ,
1152+ value : undefined ,
1153+ } ) ;
1154+ await returnPromise ;
1155+ } ) ;
1156+ it ( 'Returns underlying async iterables when dispatcher is thrown' , async ( ) => {
1157+ const document = parse ( `
1158+ query {
1159+ asyncIterableListDelayed @stream(initialCount: 1) {
1160+ name
1161+ id
1162+ }
1163+ }
1164+ ` ) ;
1165+ const schema = new GraphQLSchema ( { query } ) ;
1166+
1167+ const executeResult = await execute ( { schema, document, rootValue : { } } ) ;
1168+ invariant ( isAsyncIterable ( executeResult ) ) ;
1169+ const iterator = executeResult [ Symbol . asyncIterator ] ( ) ;
1170+
1171+ const result1 = await iterator . next ( ) ;
1172+ expectJSON ( result1 ) . toDeepEqual ( {
1173+ done : false ,
1174+ value : {
1175+ data : {
1176+ asyncIterableListDelayed : [
1177+ {
1178+ id : '1' ,
1179+ name : 'Luke' ,
1180+ } ,
1181+ ] ,
1182+ } ,
1183+ hasNext : true ,
1184+ } ,
1185+ } ) ;
1186+
1187+ const throwPromise = iterator . throw ( new Error ( 'bad' ) ) ;
1188+
1189+ // this result had started processing before return was called
1190+ const result2 = await iterator . next ( ) ;
1191+ expectJSON ( result2 ) . toDeepEqual ( {
1192+ done : false ,
1193+ value : {
1194+ data : {
1195+ id : '2' ,
1196+ name : 'Han' ,
1197+ } ,
1198+ hasNext : true ,
1199+ path : [ 'asyncIterableListDelayed' , 1 ] ,
1200+ } ,
1201+ } ) ;
1202+
1203+ // third result is not returned because async iterator has returned
1204+ const result3 = await iterator . next ( ) ;
1205+ expectJSON ( result3 ) . toDeepEqual ( {
1206+ done : true ,
1207+ value : undefined ,
1208+ } ) ;
1209+ try {
1210+ await throwPromise ; /* c8 ignore start */
1211+ // Not reachable, always throws
1212+ /* c8 ignore stop */
1213+ } catch ( e ) {
1214+ // ignore error
1215+ }
1216+ } ) ;
10141217} ) ;
0 commit comments