@@ -116,40 +116,57 @@ function instrumentFetch(onFetchResolved?: (response: Response) => void, skipNat
116
116
}
117
117
118
118
async function resolveResponse ( res : Response | undefined , onFinishedResolving : ( ) => void ) : Promise < void > {
119
- if ( res && res . body && res . body . getReader ) {
120
- const responseReader = res . body . getReader ( ) ;
121
-
122
- // eslint-disable-next-line no-inner-declarations
123
- async function consumeChunks ( { done } : { done : boolean } ) : Promise < void > {
124
- if ( ! done ) {
125
- try {
126
- // abort reading if read op takes more than 5s
127
- const result = await Promise . race ( [
128
- responseReader . read ( ) ,
129
- new Promise < { done : boolean } > ( res => {
130
- setTimeout ( ( ) => {
131
- res ( { done : true } ) ;
132
- } , 5000 ) ;
133
- } ) ,
134
- ] ) ;
135
- await consumeChunks ( result ) ;
136
- } catch ( error ) {
137
- // handle error if needed
119
+ if ( res && res . body ) {
120
+ const body = res . body ;
121
+ const responseReader = body . getReader ( ) ;
122
+
123
+ // Define a maximum duration after which we just cancel
124
+ const maxFetchDurationTimeout = setTimeout (
125
+ ( ) => {
126
+ body . cancel ( ) . then ( null , ( ) => {
127
+ // noop
128
+ } ) ;
129
+ } ,
130
+ 90 * 1000 , // 90s
131
+ ) ;
132
+
133
+ let readingActive = true ;
134
+ while ( readingActive ) {
135
+ let chunkTimeout ;
136
+ try {
137
+ // abort reading if read op takes more than 5s
138
+ chunkTimeout = setTimeout ( ( ) => {
139
+ body . cancel ( ) . then ( null , ( ) => {
140
+ // noop on error
141
+ } ) ;
142
+ } , 5000 ) ;
143
+
144
+ // This .read() call will reject/throw when we abort due to timeouts through `body.cancel()`
145
+ const { done } = await responseReader . read ( ) ;
146
+
147
+ clearTimeout ( chunkTimeout ) ;
148
+
149
+ if ( done ) {
150
+ onFinishedResolving ( ) ;
151
+ readingActive = false ;
138
152
}
139
- } else {
140
- return Promise . resolve ( ) ;
153
+ } catch ( error ) {
154
+ readingActive = false ;
155
+ } finally {
156
+ clearTimeout ( chunkTimeout ) ;
141
157
}
142
158
}
143
159
144
- return responseReader
145
- . read ( )
146
- . then ( consumeChunks )
147
- . then ( onFinishedResolving )
148
- . catch ( ( ) => undefined ) ;
160
+ clearTimeout ( maxFetchDurationTimeout ) ;
161
+
162
+ responseReader . releaseLock ( ) ;
163
+ body . cancel ( ) . then ( null , ( ) => {
164
+ // noop on error
165
+ } ) ;
149
166
}
150
167
}
151
168
152
- async function streamHandler ( response : Response ) : Promise < void > {
169
+ function streamHandler ( response : Response ) : void {
153
170
// clone response for awaiting stream
154
171
let clonedResponseForResolving : Response ;
155
172
try {
@@ -158,7 +175,8 @@ async function streamHandler(response: Response): Promise<void> {
158
175
return ;
159
176
}
160
177
161
- await resolveResponse ( clonedResponseForResolving , ( ) => {
178
+ // eslint-disable-next-line @typescript-eslint/no-floating-promises
179
+ resolveResponse ( clonedResponseForResolving , ( ) => {
162
180
triggerHandlers ( 'fetch-body-resolved' , {
163
181
endTimestamp : timestampInSeconds ( ) * 1000 ,
164
182
response,
0 commit comments