11import arrayFrom from '../polyfills/arrayFrom' ;
2+ import { SYMBOL_ASYNC_ITERATOR } from '../polyfills/symbols' ;
23
34import type { Path } from '../jsutils/Path' ;
45import type { ObjMap } from '../jsutils/ObjMap' ;
@@ -8,6 +9,7 @@ import memoize3 from '../jsutils/memoize3';
89import invariant from '../jsutils/invariant' ;
910import devAssert from '../jsutils/devAssert' ;
1011import isPromise from '../jsutils/isPromise' ;
12+ import isAsyncIterable from '../jsutils/isAsyncIterable' ;
1113import isObjectLike from '../jsutils/isObjectLike' ;
1214import isCollection from '../jsutils/isCollection' ;
1315import promiseReduce from '../jsutils/promiseReduce' ;
@@ -855,6 +857,74 @@ function completeValue(
855857 ) ;
856858}
857859
860+ /**
861+ * Complete a async iterator value by completing the result and calling
862+ * recursively until all the results are completed.
863+ */
864+ function completeAsyncIteratorValue (
865+ exeContext : ExecutionContext ,
866+ itemType : GraphQLOutputType ,
867+ fieldNodes : $ReadOnlyArray < FieldNode > ,
868+ info : GraphQLResolveInfo ,
869+ path : Path ,
870+ iterator : AsyncIterator < mixed > ,
871+ ) : Promise < $ReadOnlyArray < mixed >> {
872+ let containsPromise = false ;
873+ return new Promise ( ( resolve ) => {
874+ function next ( index , completedResults ) {
875+ const fieldPath = addPath ( path , index , undefined ) ;
876+ iterator . next ( ) . then (
877+ ( { value, done } ) => {
878+ if ( done ) {
879+ resolve ( completedResults ) ;
880+ return ;
881+ }
882+ // TODO can the error checking logic be consolidated with completeListValue?
883+ try {
884+ const completedItem = completeValue (
885+ exeContext ,
886+ itemType ,
887+ fieldNodes ,
888+ info ,
889+ fieldPath ,
890+ value ,
891+ ) ;
892+ if ( isPromise ( completedItem ) ) {
893+ containsPromise = true ;
894+ }
895+ completedResults . push ( completedItem ) ;
896+ } catch ( rawError ) {
897+ completedResults . push ( null ) ;
898+ const error = locatedError (
899+ rawError ,
900+ fieldNodes ,
901+ pathToArray ( fieldPath ) ,
902+ ) ;
903+ handleFieldError ( error , itemType , exeContext ) ;
904+ resolve ( completedResults ) ;
905+ return ;
906+ }
907+
908+ next ( index + 1 , completedResults ) ;
909+ } ,
910+ ( rawError ) => {
911+ completedResults . push ( null ) ;
912+ const error = locatedError (
913+ rawError ,
914+ fieldNodes ,
915+ pathToArray ( fieldPath ) ,
916+ ) ;
917+ handleFieldError ( error , itemType , exeContext ) ;
918+ resolve ( completedResults ) ;
919+ } ,
920+ ) ;
921+ }
922+ next ( 0 , [ ] ) ;
923+ } ) . then ( ( completedResults ) =>
924+ containsPromise ? Promise . all ( completedResults ) : completedResults ,
925+ ) ;
926+ }
927+
858928/**
859929 * Complete a list value by completing each item in the list with the
860930 * inner type
@@ -867,6 +937,21 @@ function completeListValue(
867937 path : Path ,
868938 result : mixed ,
869939) : PromiseOrValue < $ReadOnlyArray < mixed >> {
940+ const itemType = returnType . ofType ;
941+
942+ if ( isAsyncIterable ( result ) ) {
943+ const iterator = result [ SYMBOL_ASYNC_ITERATOR ] ( ) ;
944+
945+ return completeAsyncIteratorValue (
946+ exeContext ,
947+ itemType ,
948+ fieldNodes ,
949+ info ,
950+ path ,
951+ iterator ,
952+ ) ;
953+ }
954+
870955 if ( ! isCollection ( result ) ) {
871956 throw new GraphQLError (
872957 `Expected Iterable, but did not find one for field "${ info . parentType . name } .${ info . fieldName } ".` ,
@@ -875,7 +960,6 @@ function completeListValue(
875960
876961 // This is specified as a simple map, however we're optimizing the path
877962 // where the list contains no Promises by avoiding creating another Promise.
878- const itemType = returnType . ofType ;
879963 let containsPromise = false ;
880964 const completedResults = arrayFrom ( result , ( item , index ) => {
881965 // No need to modify the info object containing the path,
0 commit comments