1
- export interface Response {
1
+ export interface WrappedResponse {
2
2
status : number ;
3
3
statusText ?: string ;
4
4
text ?( ) : Promise < string > ;
@@ -7,29 +7,105 @@ export interface Response {
7
7
}
8
8
9
9
export type FetchFn = (
10
- url : URL ,
10
+ url : URL | string ,
11
11
...args : any [ ]
12
- ) => Promise < Response | globalThis . Response > ;
12
+ ) => Promise < WrappedResponse | globalThis . Response >
13
13
14
- let retryCount = 3 ;
14
+ export type WrappedFetch = ( (
15
+ url : URL | string ,
16
+ ...args : any [ ]
17
+ ) => Promise < WrappedResponse | globalThis . Response > ) & {
18
+ arrayBuffer : ( url : URL | string , ...args : any [ ] ) => Promise < ArrayBuffer | null > ,
19
+ text : ( url : URL | string , ...args : any [ ] ) => Promise < string | null >
20
+ } ;
21
+
22
+ let retryCount = 5 , poolSize = 100 ;
15
23
16
24
export function setRetryCount ( count : number ) {
17
25
retryCount = count ;
18
26
}
19
27
28
+ export function setFetchPoolSize ( size : number ) {
29
+ poolSize = size ;
30
+ }
31
+
20
32
/**
21
- * Wraps a fetch request with retry logic on exceptions, which is useful for
22
- * spotty connections that may fail intermittently.
33
+ * Wraps a fetch request with pooling, and retry logic on exceptions (emfile / network errors).
23
34
*/
24
- export function wrapWithRetry ( fetch : FetchFn ) : FetchFn {
25
- return async function ( url : URL , ...args : any [ ] ) {
35
+ export function wrappedFetch ( fetch : FetchFn ) : WrappedFetch {
36
+ const wrappedFetch = async function ( url : URL | string , ...args : any [ ] ) {
37
+ url = url . toString ( ) ;
26
38
let retries = 0 ;
27
- while ( true ) {
28
- try {
29
- return await fetch ( url , ...args ) ;
30
- } catch ( e ) {
31
- if ( retries ++ >= retryCount ) throw e ;
39
+ try {
40
+ await pushFetchPool ( ) ;
41
+ while ( true ) {
42
+ try {
43
+ return await fetch ( url , ...args ) ;
44
+ } catch ( e ) {
45
+ if ( retries ++ >= retryCount ) throw e ;
46
+ }
32
47
}
48
+ } finally {
49
+ popFetchPool ( ) ;
33
50
}
34
51
} ;
52
+ wrappedFetch . arrayBuffer = async function ( url , ...args ) {
53
+ url = url . toString ( ) ;
54
+ let retries = 0 ;
55
+ try {
56
+ await pushFetchPool ( ) ;
57
+ while ( true ) {
58
+ try {
59
+ var res = await fetch ( url , ...args ) ;
60
+ } catch ( e ) {
61
+ if ( retries ++ >= retryCount )
62
+ throw e ;
63
+ continue ;
64
+ }
65
+ switch ( res . status ) {
66
+ case 200 :
67
+ case 304 :
68
+ break ;
69
+ // not found = null
70
+ case 404 :
71
+ return null ;
72
+ default :
73
+ throw new Error ( `Invalid status code ${ res . status } ` ) ;
74
+ }
75
+ try {
76
+ return await res . arrayBuffer ( ) ;
77
+ } catch ( e ) {
78
+ if ( retries ++ >= retryCount &&
79
+ e . code === "ERR_SOCKET_TIMEOUT" ||
80
+ e . code === "ETIMEOUT" ||
81
+ e . code === "ECONNRESET" ||
82
+ e . code === 'FETCH_ERROR' ) {
83
+
84
+ }
85
+ }
86
+ }
87
+ } finally {
88
+ popFetchPool ( ) ;
89
+ }
90
+ } ;
91
+ wrappedFetch . text = async function ( url , ...args ) {
92
+ const arrayBuffer = await this . arrayBuffer ( url , ...args ) ;
93
+ if ( ! arrayBuffer )
94
+ return null ;
95
+ return new TextDecoder ( ) . decode ( arrayBuffer ) ;
96
+ } ;
97
+ return wrappedFetch ;
98
+ }
99
+
100
+ // restrict in-flight fetches to a pool of 100
101
+ let p = [ ] ;
102
+ let c = 0 ;
103
+ function pushFetchPool ( ) {
104
+ if ( ++ c > poolSize )
105
+ return new Promise ( r => p . push ( r ) ) ;
106
+ }
107
+ function popFetchPool ( ) {
108
+ c -- ;
109
+ if ( p . length )
110
+ p . shift ( ) ( ) ;
35
111
}
0 commit comments