@@ -15,21 +15,41 @@ const AbortController = require('abort-controller');
15
15
const fetch = require ( 'node-fetch' ) ;
16
16
const { FetchError} = fetch ;
17
17
18
+ function getTimeRemaining ( retryOptions ) {
19
+ if ( retryOptions && retryOptions . startTime && retryOptions . retryMaxDuration ) {
20
+ const millisEllapsed = Date . now ( ) - retryOptions . startTime ;
21
+ const remaining = retryOptions . retryMaxDuration - millisEllapsed ;
22
+ return Math . max ( 0 , remaining ) ;
23
+ } else {
24
+ return Infinity ;
25
+ }
26
+ }
27
+
28
+ /**
29
+ * Have we exceeded the max duration for this fetch operation?
30
+ * @param {* } retryOptions Options including retryMaxDuration and startTime
31
+ * @returns True if we have a max duration set and it is exceeded, otherwise false
32
+ */
33
+ function isResponseTimedOut ( retryOptions ) {
34
+ return getTimeRemaining ( retryOptions ) <= 0 ;
35
+ }
36
+
18
37
/**
19
- * Retry
38
+ * shouldRetry
20
39
* @param {RetryOptions } retryOptions whether or not to retry on all http error codes or just >500
21
40
* @param {Object } error error object if the fetch request returned an error
22
41
* @param {Object } response fetch call response
42
+ * @param {Number } wait Amount of time we will wait before retrying next
23
43
* @returns {Boolean } whether or not to retry the request
24
44
*/
25
- function retry ( retryOptions , error , response ) {
26
- if ( retryOptions ) {
27
- const totalMillisToWait = ( retryOptions . retryInitialDelay ) + ( Date . now ( ) - retryOptions . startTime ) ;
28
- return ( ( totalMillisToWait < retryOptions . retryMaxDuration ) &&
29
- ( error !== null || ( retryOptions . retryOnHttpResponse && ( retryOptions . retryOnHttpResponse ( response ) ) ) )
30
- ) ;
45
+ function shouldRetry ( retryOptions , error , response , waitTime ) {
46
+ if ( getTimeRemaining ( retryOptions ) < waitTime ) {
47
+ return false ;
48
+ } else if ( retryOptions && retryOptions . retryOnHttpResponse ) {
49
+ return retryOptions . retryOnHttpResponse ( response ) ;
50
+ } else {
51
+ return false ;
31
52
}
32
- return false ;
33
53
}
34
54
35
55
/**
@@ -84,7 +104,7 @@ function retryInit(options={}) {
84
104
* @param {RetryOptions|Boolean } retryOptions Retry options
85
105
* @param {Boolean } [random=true] Add randomness
86
106
*/
87
- function retryDelay ( retryOptions , random = true ) {
107
+ function getRetryDelay ( retryOptions , random = true ) {
88
108
return retryOptions . retryInitialDelay +
89
109
( random ? Math . floor ( Math . random ( ) * 100 ) : 99 ) ;
90
110
}
@@ -147,43 +167,47 @@ module.exports = async function (url, options) {
147
167
148
168
return new Promise ( function ( resolve , reject ) {
149
169
const wrappedFetch = async ( ) => {
150
- ++ attempt ;
151
-
152
- let timeoutHandler ;
153
- if ( retryOptions . socketTimeout ) {
154
- const controller = new AbortController ( ) ;
155
- timeoutHandler = setTimeout ( ( ) => controller . abort ( ) , retryOptions . socketTimeout ) ;
156
- options . signal = controller . signal ;
157
- }
158
-
159
- try {
160
- const response = await fetch ( url , options ) ;
161
- clearTimeout ( timeoutHandler ) ;
162
-
163
- if ( ! retry ( retryOptions , null , response ) ) {
164
- // response.timeout should reflect the actual timeout
165
- response . timeout = retryOptions . socketTimeout ;
166
- return resolve ( response ) ;
167
- }
168
-
169
- console . error ( `Retrying in ${ retryOptions . retryInitialDelay } milliseconds, attempt ${ attempt - 1 } failed (status ${ response . status } ): ${ response . statusText } ` ) ;
170
- } catch ( error ) {
171
- clearTimeout ( timeoutHandler ) ;
172
-
173
- if ( ! retry ( retryOptions , error , null ) ) {
174
- if ( error . name === 'AbortError' ) {
175
- return reject ( new FetchError ( `network timeout at ${ url } ` , 'request-timeout' ) ) ;
170
+ while ( ! isResponseTimedOut ( retryOptions ) ) {
171
+ ++ attempt ;
172
+ const waitTime = getRetryDelay ( retryOptions ) ;
173
+
174
+ let timeoutHandler ;
175
+ if ( retryOptions . socketTimeout ) {
176
+ const controller = new AbortController ( ) ;
177
+ timeoutHandler = setTimeout ( ( ) => controller . abort ( ) , retryOptions . socketTimeout ) ;
178
+ options . signal = controller . signal ;
179
+ }
180
+
181
+ try {
182
+ const response = await fetch ( url , options ) ;
183
+
184
+ if ( shouldRetry ( retryOptions , null , response , waitTime ) ) {
185
+ console . error ( `Retrying in ${ waitTime } milliseconds, attempt ${ attempt } failed (status ${ response . status } ): ${ response . statusText } ` ) ;
186
+ } else {
187
+ // response.timeout should reflect the actual timeout
188
+ response . timeout = retryOptions . socketTimeout ;
189
+ return resolve ( response ) ;
176
190
}
177
-
178
- return reject ( error ) ;
191
+ } catch ( error ) {
192
+ const response = { status : error . code || 500 , statusMessage : error . message || "Unknown server error" } ;
193
+ if ( ! shouldRetry ( retryOptions , error , response , waitTime ) ) {
194
+ if ( error . name === 'AbortError' ) {
195
+ return reject ( new FetchError ( `network timeout at ${ url } ` , 'request-timeout' ) ) ;
196
+ } else {
197
+ return reject ( error ) ;
198
+ }
199
+ }
200
+ console . error ( `Retrying in ${ waitTime } milliseconds, attempt ${ attempt } error: ${ error . message } ` ) ;
201
+ } finally {
202
+ clearTimeout ( timeoutHandler ) ;
179
203
}
180
-
181
- console . error ( `Retrying in ${ retryOptions . retryInitialDelay } milliseconds, attempt ${ attempt - 1 } error: ${ error . message } ` ) ;
204
+ // Fetch loop is about to repeat, delay as needed first.
205
+ if ( waitTime > 0 ) {
206
+ await new Promise ( resolve => setTimeout ( resolve , waitTime ) ) ;
207
+ }
208
+ retryOptions . retryInitialDelay *= retryOptions . retryBackoff ; // update retry interval
182
209
}
183
-
184
- retryOptions . retryInitialDelay *= retryOptions . retryBackoff ; // update retry interval
185
- const waitTime = retryDelay ( retryOptions ) ;
186
- setTimeout ( ( ) => { wrappedFetch ( ) ; } , waitTime ) ;
210
+ reject ( new FetchError ( `network timeout at ${ url } ` , 'request-timeout' ) ) ;
187
211
} ;
188
212
wrappedFetch ( ) ;
189
213
} ) ;
0 commit comments