@@ -635,6 +635,299 @@ describe('Execute: Handles basic execution tasks', () => {
635
635
expect ( isAsyncResolverFinished ) . to . equal ( true ) ;
636
636
} ) ;
637
637
638
+ it ( 'exits early on early abort' , ( ) => {
639
+ let isExecuted = false ;
640
+
641
+ const schema = new GraphQLSchema ( {
642
+ query : new GraphQLObjectType ( {
643
+ name : 'Query' ,
644
+ fields : {
645
+ field : {
646
+ type : GraphQLString ,
647
+ /* c8 ignore next 3 */
648
+ resolve ( ) {
649
+ isExecuted = true ;
650
+ } ,
651
+ } ,
652
+ } ,
653
+ } ) ,
654
+ } ) ;
655
+
656
+ const document = parse ( `
657
+ {
658
+ field
659
+ }
660
+ ` ) ;
661
+
662
+ const abortController = new AbortController ( ) ;
663
+ abortController . abort ( ) ;
664
+
665
+ const result = execute ( {
666
+ schema,
667
+ document,
668
+ abortSignal : abortController . signal ,
669
+ } ) ;
670
+
671
+ expect ( isExecuted ) . to . equal ( false ) ;
672
+ expectJSON ( result ) . toDeepEqual ( {
673
+ data : { field : null } ,
674
+ errors : [
675
+ {
676
+ message : 'This operation was aborted' ,
677
+ locations : [ { line : 3 , column : 9 } ] ,
678
+ path : [ 'field' ] ,
679
+ } ,
680
+ ] ,
681
+ } ) ;
682
+ } ) ;
683
+
684
+ it ( 'exits early on abort mid-execution' , async ( ) => {
685
+ let isExecuted = false ;
686
+
687
+ const asyncObjectType = new GraphQLObjectType ( {
688
+ name : 'AsyncObject' ,
689
+ fields : {
690
+ field : {
691
+ type : GraphQLString ,
692
+ /* c8 ignore next 3 */
693
+ resolve ( ) {
694
+ isExecuted = true ;
695
+ } ,
696
+ } ,
697
+ } ,
698
+ } ) ;
699
+
700
+ const schema = new GraphQLSchema ( {
701
+ query : new GraphQLObjectType ( {
702
+ name : 'Query' ,
703
+ fields : {
704
+ asyncObject : {
705
+ type : asyncObjectType ,
706
+ async resolve ( ) {
707
+ await resolveOnNextTick ( ) ;
708
+ return { } ;
709
+ } ,
710
+ } ,
711
+ } ,
712
+ } ) ,
713
+ } ) ;
714
+
715
+ const document = parse ( `
716
+ {
717
+ asyncObject {
718
+ field
719
+ }
720
+ }
721
+ ` ) ;
722
+
723
+ const abortController = new AbortController ( ) ;
724
+
725
+ const result = execute ( {
726
+ schema,
727
+ document,
728
+ abortSignal : abortController . signal ,
729
+ } ) ;
730
+
731
+ abortController . abort ( ) ;
732
+
733
+ expect ( isExecuted ) . to . equal ( false ) ;
734
+ expectJSON ( await result ) . toDeepEqual ( {
735
+ data : { asyncObject : { field : null } } ,
736
+ errors : [
737
+ {
738
+ message : 'This operation was aborted' ,
739
+ locations : [ { line : 4 , column : 11 } ] ,
740
+ path : [ 'asyncObject' , 'field' ] ,
741
+ } ,
742
+ ] ,
743
+ } ) ;
744
+ expect ( isExecuted ) . to . equal ( false ) ;
745
+ } ) ;
746
+
747
+ it ( 'exits early on abort mid-resolver' , async ( ) => {
748
+ const schema = new GraphQLSchema ( {
749
+ query : new GraphQLObjectType ( {
750
+ name : 'Query' ,
751
+ fields : {
752
+ asyncField : {
753
+ type : GraphQLString ,
754
+ async resolve ( _parent , _args , _context , _info , abortSignal ) {
755
+ await resolveOnNextTick ( ) ;
756
+ abortSignal ?. throwIfAborted ( ) ;
757
+ } ,
758
+ } ,
759
+ } ,
760
+ } ) ,
761
+ } ) ;
762
+
763
+ const document = parse ( `
764
+ {
765
+ asyncField
766
+ }
767
+ ` ) ;
768
+
769
+ const abortController = new AbortController ( ) ;
770
+
771
+ const result = execute ( {
772
+ schema,
773
+ document,
774
+ abortSignal : abortController . signal ,
775
+ } ) ;
776
+
777
+ abortController . abort ( ) ;
778
+
779
+ expectJSON ( await result ) . toDeepEqual ( {
780
+ data : { asyncField : null } ,
781
+ errors : [
782
+ {
783
+ message : 'This operation was aborted' ,
784
+ locations : [ { line : 3 , column : 9 } ] ,
785
+ path : [ 'asyncField' ] ,
786
+ } ,
787
+ ] ,
788
+ } ) ;
789
+ } ) ;
790
+
791
+ it ( 'exits early on abort mid-nested resolver' , async ( ) => {
792
+ const syncObjectType = new GraphQLObjectType ( {
793
+ name : 'SyncObject' ,
794
+ fields : {
795
+ asyncField : {
796
+ type : GraphQLString ,
797
+ async resolve ( _parent , _args , _context , _info , abortSignal ) {
798
+ await resolveOnNextTick ( ) ;
799
+ abortSignal ?. throwIfAborted ( ) ;
800
+ } ,
801
+ } ,
802
+ } ,
803
+ } ) ;
804
+
805
+ const schema = new GraphQLSchema ( {
806
+ query : new GraphQLObjectType ( {
807
+ name : 'Query' ,
808
+ fields : {
809
+ syncObject : {
810
+ type : syncObjectType ,
811
+ resolve ( ) {
812
+ return { } ;
813
+ } ,
814
+ } ,
815
+ } ,
816
+ } ) ,
817
+ } ) ;
818
+
819
+ const document = parse ( `
820
+ {
821
+ syncObject {
822
+ asyncField
823
+ }
824
+ }
825
+ ` ) ;
826
+
827
+ const abortController = new AbortController ( ) ;
828
+
829
+ const result = execute ( {
830
+ schema,
831
+ document,
832
+ abortSignal : abortController . signal ,
833
+ } ) ;
834
+
835
+ abortController . abort ( ) ;
836
+
837
+ expectJSON ( await result ) . toDeepEqual ( {
838
+ data : { syncObject : { asyncField : null } } ,
839
+ errors : [
840
+ {
841
+ message : 'This operation was aborted' ,
842
+ locations : [ { line : 4 , column : 11 } ] ,
843
+ path : [ 'syncObject' , 'asyncField' ] ,
844
+ } ,
845
+ ] ,
846
+ } ) ;
847
+ } ) ;
848
+
849
+ it ( 'exits early on error' , async ( ) => {
850
+ const objectType = new GraphQLObjectType ( {
851
+ name : 'Object' ,
852
+ fields : {
853
+ nonNullNestedAsyncField : {
854
+ type : new GraphQLNonNull ( GraphQLString ) ,
855
+ async resolve ( ) {
856
+ await resolveOnNextTick ( ) ;
857
+ throw new Error ( 'Oops' ) ;
858
+ } ,
859
+ } ,
860
+ nestedAsyncField : {
861
+ type : GraphQLString ,
862
+ async resolve ( _parent , _args , _context , _info , abortSignal ) {
863
+ await resolveOnNextTick ( ) ;
864
+ abortSignal ?. throwIfAborted ( ) ;
865
+ } ,
866
+ } ,
867
+ } ,
868
+ } ) ;
869
+
870
+ const schema = new GraphQLSchema ( {
871
+ query : new GraphQLObjectType ( {
872
+ name : 'Query' ,
873
+ fields : {
874
+ object : {
875
+ type : objectType ,
876
+ resolve ( ) {
877
+ return { } ;
878
+ } ,
879
+ } ,
880
+ asyncField : {
881
+ type : GraphQLString ,
882
+ async resolve ( ) {
883
+ await resolveOnNextTick ( ) ;
884
+ return 'asyncValue' ;
885
+ } ,
886
+ } ,
887
+ } ,
888
+ } ) ,
889
+ } ) ;
890
+
891
+ const document = parse ( `
892
+ {
893
+ object {
894
+ nonNullNestedAsyncField
895
+ nestedAsyncField
896
+ }
897
+ asyncField
898
+ }
899
+ ` ) ;
900
+
901
+ const abortController = new AbortController ( ) ;
902
+
903
+ const result = execute ( {
904
+ schema,
905
+ document,
906
+ abortSignal : abortController . signal ,
907
+ } ) ;
908
+
909
+ abortController . abort ( ) ;
910
+
911
+ expectJSON ( await result ) . toDeepEqual ( {
912
+ data : {
913
+ object : null ,
914
+ asyncField : 'asyncValue' ,
915
+ } ,
916
+ errors : [
917
+ {
918
+ message : 'This operation was aborted' ,
919
+ locations : [ { line : 5 , column : 11 } ] ,
920
+ path : [ 'object' , 'nestedAsyncField' ] ,
921
+ } ,
922
+ {
923
+ message : 'Oops' ,
924
+ locations : [ { line : 4 , column : 11 } ] ,
925
+ path : [ 'object' , 'nonNullNestedAsyncField' ] ,
926
+ } ,
927
+ ] ,
928
+ } ) ;
929
+ } ) ;
930
+
638
931
it ( 'Full response path is included for non-nullable fields' , ( ) => {
639
932
const A : GraphQLObjectType = new GraphQLObjectType ( {
640
933
name : 'A' ,
0 commit comments