@@ -895,9 +895,13 @@ export interface TrackedPromise extends Promise<any> {
895
895
_error ?: any ;
896
896
}
897
897
898
+ export class AbortedDeferredError extends Error { }
899
+
898
900
export class DeferredData {
899
901
private pendingKeys : Set < string | number > = new Set < string | number > ( ) ;
900
- private cancelled : boolean = false ;
902
+ private controller : AbortController ;
903
+ private abortPromise : Promise < void > ;
904
+ private unlistenAbortSignal : ( ) => void ;
901
905
private subscriber ?: ( aborted : boolean ) => void = undefined ;
902
906
data : Record < string , unknown > ;
903
907
@@ -906,6 +910,18 @@ export class DeferredData {
906
910
data && typeof data === "object" && ! Array . isArray ( data ) ,
907
911
"defer() only accepts plain objects"
908
912
) ;
913
+
914
+ // Set up an AbortController + Promise we can race against to exit early
915
+ // cancellation
916
+ let reject : ( e : AbortedDeferredError ) => void ;
917
+ this . abortPromise = new Promise ( ( _ , r ) => ( reject = r ) ) ;
918
+ this . controller = new AbortController ( ) ;
919
+ let onAbort = ( ) =>
920
+ reject ( new AbortedDeferredError ( "Deferred data aborted" ) ) ;
921
+ this . unlistenAbortSignal = ( ) =>
922
+ this . controller . signal . removeEventListener ( "abort" , onAbort ) ;
923
+ this . controller . signal . addEventListener ( "abort" , onAbort ) ;
924
+
909
925
this . data = Object . entries ( data ) . reduce (
910
926
( acc , [ key , value ] ) =>
911
927
Object . assign ( acc , {
@@ -927,10 +943,15 @@ export class DeferredData {
927
943
928
944
// We store a little wrapper promise that will be extended with
929
945
// _data/_error props upon resolve/reject
930
- let promise : TrackedPromise = value . then (
946
+ let promise : TrackedPromise = Promise . race ( [ value , this . abortPromise ] ) . then (
931
947
( data ) => this . onSettle ( promise , key , null , data as unknown ) ,
932
948
( error ) => this . onSettle ( promise , key , error as unknown )
933
949
) ;
950
+
951
+ // Register rejection listeners to avoid uncaught promise rejections on
952
+ // errors or aborted deferred values
953
+ promise . catch ( ( ) => { } ) ;
954
+
934
955
Object . defineProperty ( promise , "_tracked" , { get : ( ) => true } ) ;
935
956
return promise ;
936
957
}
@@ -940,27 +961,39 @@ export class DeferredData {
940
961
key : string | number ,
941
962
error : unknown ,
942
963
data ?: unknown
943
- ) : void {
944
- if ( this . cancelled ) {
945
- return ;
964
+ ) : unknown {
965
+ if (
966
+ this . controller . signal . aborted &&
967
+ error instanceof AbortedDeferredError
968
+ ) {
969
+ this . unlistenAbortSignal ( ) ;
970
+ return Promise . reject ( error ) ;
946
971
}
972
+
947
973
this . pendingKeys . delete ( key ) ;
948
974
975
+ if ( this . done ) {
976
+ // Nothing left to abort!
977
+ this . unlistenAbortSignal ( ) ;
978
+ }
979
+
949
980
if ( error ) {
950
981
Object . defineProperty ( promise , "_error" , { get : ( ) => error } ) ;
951
- } else {
952
- Object . defineProperty ( promise , "_data" , { get : ( ) => data } ) ;
982
+ this . subscriber ?. ( false ) ;
983
+ return Promise . reject ( error ) ;
953
984
}
954
985
986
+ Object . defineProperty ( promise , "_data" , { get : ( ) => data } ) ;
955
987
this . subscriber ?.( false ) ;
988
+ return data ;
956
989
}
957
990
958
991
subscribe ( fn : ( aborted : boolean ) => void ) {
959
992
this . subscriber = fn ;
960
993
}
961
994
962
995
cancel ( ) {
963
- this . cancelled = true ;
996
+ this . controller . abort ( ) ;
964
997
this . pendingKeys . forEach ( ( v , k ) => this . pendingKeys . delete ( k ) ) ;
965
998
this . subscriber ?.( true ) ;
966
999
}
0 commit comments