1+ export class CancelError extends Error {
2+ constructor ( message : string ) {
3+ super ( message ) ;
4+ this . name = 'CancelError' ;
5+ }
6+
7+ public get isCancelled ( ) : boolean {
8+ return true ;
9+ }
10+ }
11+
12+ export interface OnCancel {
13+ readonly isResolved : boolean ;
14+ readonly isRejected : boolean ;
15+ readonly isCancelled : boolean ;
16+
17+ ( cancelHandler : ( ) => void ) : void ;
18+ }
19+
20+ export class CancelablePromise < T > implements Promise < T > {
21+ private _isResolved : boolean ;
22+ private _isRejected : boolean ;
23+ private _isCancelled : boolean ;
24+ readonly cancelHandlers : ( ( ) => void ) [ ] ;
25+ readonly promise : Promise < T > ;
26+ private _resolve ?: ( value : T | PromiseLike < T > ) => void ;
27+ private _reject ?: ( reason ?: unknown ) => void ;
28+
29+ constructor (
30+ executor : (
31+ resolve : ( value : T | PromiseLike < T > ) => void ,
32+ reject : ( reason ?: unknown ) => void ,
33+ onCancel : OnCancel
34+ ) => void
35+ ) {
36+ this . _isResolved = false ;
37+ this . _isRejected = false ;
38+ this . _isCancelled = false ;
39+ this . cancelHandlers = [ ] ;
40+ this . promise = new Promise < T > ( ( resolve , reject ) => {
41+ this . _resolve = resolve ;
42+ this . _reject = reject ;
43+
44+ const onResolve = ( value : T | PromiseLike < T > ) : void => {
45+ if ( this . _isResolved || this . _isRejected || this . _isCancelled ) {
46+ return ;
47+ }
48+ this . _isResolved = true ;
49+ if ( this . _resolve ) this . _resolve ( value ) ;
50+ } ;
51+
52+ const onReject = ( reason ?: unknown ) : void => {
53+ if ( this . _isResolved || this . _isRejected || this . _isCancelled ) {
54+ return ;
55+ }
56+ this . _isRejected = true ;
57+ if ( this . _reject ) this . _reject ( reason ) ;
58+ } ;
59+
60+ const onCancel = ( cancelHandler : ( ) => void ) : void => {
61+ if ( this . _isResolved || this . _isRejected || this . _isCancelled ) {
62+ return ;
63+ }
64+ this . cancelHandlers . push ( cancelHandler ) ;
65+ } ;
66+
67+ Object . defineProperty ( onCancel , 'isResolved' , {
68+ get : ( ) : boolean => this . _isResolved ,
69+ } ) ;
70+
71+ Object . defineProperty ( onCancel , 'isRejected' , {
72+ get : ( ) : boolean => this . _isRejected ,
73+ } ) ;
74+
75+ Object . defineProperty ( onCancel , 'isCancelled' , {
76+ get : ( ) : boolean => this . _isCancelled ,
77+ } ) ;
78+
79+ return executor ( onResolve , onReject , onCancel as OnCancel ) ;
80+ } ) ;
81+ }
82+
83+ get [ Symbol . toStringTag ] ( ) : string {
84+ return "Cancellable Promise" ;
85+ }
86+
87+ public then < TResult1 = T , TResult2 = never > (
88+ onFulfilled ?: ( ( value : T ) => TResult1 | PromiseLike < TResult1 > ) | null ,
89+ onRejected ?: ( ( reason : unknown ) => TResult2 | PromiseLike < TResult2 > ) | null
90+ ) : Promise < TResult1 | TResult2 > {
91+ return this . promise . then ( onFulfilled , onRejected ) ;
92+ }
93+
94+ public catch < TResult = never > (
95+ onRejected ?: ( ( reason : unknown ) => TResult | PromiseLike < TResult > ) | null
96+ ) : Promise < T | TResult > {
97+ return this . promise . catch ( onRejected ) ;
98+ }
99+
100+ public finally ( onFinally ?: ( ( ) => void ) | null ) : Promise < T > {
101+ return this . promise . finally ( onFinally ) ;
102+ }
103+
104+ public cancel ( ) : void {
105+ if ( this . _isResolved || this . _isRejected || this . _isCancelled ) {
106+ return ;
107+ }
108+ this . _isCancelled = true ;
109+ if ( this . cancelHandlers . length ) {
110+ try {
111+ for ( const cancelHandler of this . cancelHandlers ) {
112+ cancelHandler ( ) ;
113+ }
114+ } catch ( error ) {
115+ console . warn ( 'Cancellation threw an error' , error ) ;
116+ return ;
117+ }
118+ }
119+ this . cancelHandlers . length = 0 ;
120+ if ( this . _reject ) this . _reject ( new CancelError ( 'Request aborted' ) ) ;
121+ }
122+
123+ public get isCancelled ( ) : boolean {
124+ return this . _isCancelled ;
125+ }
126+ }
0 commit comments