@@ -50,16 +50,17 @@ export class PollingWithTimeoutAndAbort {
5050 const pollInterval = pollingOptions . pollInterval ?? this . #pollInterval;
5151
5252 // clean up the request entry if it exists
53- this . abortPendingRequest ( requestId ) ;
53+ await this . abortPendingRequest ( requestId ) ;
5454
5555 // insert the request entry for the next polling cycle
5656 const { abortController } = this . #insertRequestEntry( requestId , timeout ) ;
5757
5858 while ( ! abortController . signal . aborted ) {
5959 try {
6060 const result = await requestFn ( abortController . signal ) ;
61- // polling success, we just need to clean up the request entry and return the result
62- this . #cleanUpOnFinished( requestId ) ;
61+ // polling success, we just need to abort the request and return the result
62+ // note: this will trigger the abort handler, which will clean up the request entry
63+ abortController . abort ( ) ;
6364 return result ;
6465 } catch {
6566 if ( abortController . signal . aborted ) {
@@ -82,19 +83,30 @@ export class PollingWithTimeoutAndAbort {
8283 }
8384
8485 /**
85- * Abort the pending requests .
86+ * Abort the pending request .
8687 * This will clean up the request entry if it exists, and abort the pending request if it exists.
88+ * Note: this needs to be called before making a new request (with await) to avoid race conditions with the new request.
8789 *
8890 * @param requestId - The ID of the request to abort.
91+ * @returns A promise that resolves when the request is aborted.
8992 */
90- abortPendingRequest ( requestId : string ) {
91- // firstly clean up the request entry if it exists
92- // note: this does not abort the request, it only cleans up the request entry for the next polling cycle
93- const existingEntry = this . #cleanUpRequestEntryIfExists( requestId ) ;
94- // then abort the request if it exists
95- // note: this does abort the request, but it will not trigger the abort handler (hence, {@link cleanUpRequestEntryIfExists} will not be called)
96- // coz the AbortHandler event listener is already removed from the AbortSignal
97- existingEntry ?. abortController . abort ( this . ABORT_REASON_CANCELLED ) ;
93+ async abortPendingRequest ( requestId : string ) {
94+ return new Promise ( ( resolve ) => {
95+ // firstly clean up the request entry if it exists
96+ // note: this does not abort the request, it only cleans up the request entry from the map.
97+ const removedEntry = this . #cleanUp( requestId ) ;
98+ // if there's no existing entry, then we can just resolve the promise
99+ if ( ! removedEntry ) {
100+ resolve ( undefined ) ;
101+ return ;
102+ }
103+ // otherwise, we will abort the existing/pending request
104+ removedEntry . abortController . abort ( this . ABORT_REASON_CANCELLED ) ;
105+ // we want to make sure that the request is actually aborted, before inserting the new request entry
106+ if ( removedEntry . abortController . signal . aborted ) {
107+ resolve ( undefined ) ;
108+ }
109+ } ) ;
98110 }
99111
100112 /**
@@ -115,7 +127,7 @@ export class PollingWithTimeoutAndAbort {
115127
116128 // Set the abort handler and listen to the `abort` event
117129 const abortHandler = ( ) => {
118- this . #cleanUpOnFinished ( requestId ) ;
130+ this . #cleanUp ( requestId ) ;
119131 } ;
120132 abortController . signal . addEventListener ( 'abort' , abortHandler ) ;
121133
@@ -132,35 +144,21 @@ export class PollingWithTimeoutAndAbort {
132144 }
133145
134146 /**
135- * Clean up the request entry upon finished (success or failure) .
136- * This will remove the abort handler from the AbortSignal, clear the timeout , and remove the request entry.
147+ * Clean up the request entry.
148+ * This will clear the timeout, remove the abort handler event listener , and remove the request entry from the map .
137149 *
138- * @param requestId - The ID of the request to clean up for .
139- * @returns The request entry that was cleaned up, if it exists .
150+ * @param requestId - The ID of the request to clean up.
151+ * @returns The request entry that was cleaned up.
140152 */
141- #cleanUpOnFinished ( requestId : string ) : RequestEntry | undefined {
142- const requestEntry = this . #cleanUpRequestEntryIfExists ( requestId ) ;
153+ #cleanUp ( requestId : string ) {
154+ const requestEntry = this . #requestEntries . get ( requestId ) ;
143155 if ( requestEntry ) {
156+ clearTimeout ( requestEntry . timerId ) ; // clear the timeout
144157 requestEntry . abortController . signal . removeEventListener (
145158 'abort' ,
146159 requestEntry . abortHandler ,
147- ) ;
148- }
149- return requestEntry ;
150- }
151-
152- /**
153- * Clean up the request entry if it exists.
154- * This will clear the pending timeout, remove the event listener from the AbortSignal, and remove the request entry.
155- *
156- * @param requestId - The ID of the request to handle the abort for.
157- * @returns The request entry that was aborted, if it exists.
158- */
159- #cleanUpRequestEntryIfExists( requestId : string ) : RequestEntry | undefined {
160- const requestEntry = this . #requestEntries. get ( requestId ) ;
161- if ( requestEntry ) {
162- clearTimeout ( requestEntry . timerId ) ; // Clear the timeout
163- this . #requestEntries. delete ( requestId ) ; // Remove the request entry
160+ ) ; // clean up the abort handler event listener
161+ this . #requestEntries. delete ( requestId ) ; // remove the request entry
164162 }
165163 return requestEntry ;
166164 }
0 commit comments