@@ -5,7 +5,6 @@ import type {
5
5
FetchAction ,
6
6
Manager ,
7
7
ActionTypes ,
8
- MiddlewareAPI ,
9
8
Middleware ,
10
9
SetResponseAction ,
11
10
} from '../types.js' ;
@@ -18,6 +17,13 @@ export class ResetError extends Error {
18
17
}
19
18
}
20
19
20
+ export interface FetchingMeta {
21
+ promise : Promise < any > ;
22
+ resolve : ( value ?: any ) => void ;
23
+ reject : ( value ?: any ) => void ;
24
+ fetchedAt : number ;
25
+ }
26
+
21
27
/** Handles all async network dispatches
22
28
*
23
29
* Dedupes concurrent requests by keeping track of all fetches in flight
@@ -28,10 +34,7 @@ export class ResetError extends Error {
28
34
* @see https://dataclient.io/docs/api/NetworkManager
29
35
*/
30
36
export default class NetworkManager implements Manager {
31
- protected fetched : { [ k : string ] : Promise < any > } = Object . create ( null ) ;
32
- protected resolvers : { [ k : string ] : ( value ?: any ) => void } = { } ;
33
- protected rejectors : { [ k : string ] : ( value ?: any ) => void } = { } ;
34
- protected fetchedAt : { [ k : string ] : number } = { } ;
37
+ protected fetching : Map < string , FetchingMeta > = new Map ( ) ;
35
38
declare readonly dataExpiryLength : number ;
36
39
declare readonly errorExpiryLength : number ;
37
40
protected controller : Controller = new Controller ( ) ;
@@ -61,7 +64,7 @@ export default class NetworkManager implements Manager {
61
64
case SET_RESPONSE :
62
65
// only set after new state is computed
63
66
return next ( action ) . then ( ( ) => {
64
- if ( action . key in this . fetched ) {
67
+ if ( this . fetching . has ( action . key ) ) {
65
68
// Note: meta *must* be set by reducer so this should be safe
66
69
const error = controller . getState ( ) . meta [ action . key ] ?. error ;
67
70
// processing errors result in state meta having error, so we should reject the promise
@@ -80,14 +83,16 @@ export default class NetworkManager implements Manager {
80
83
}
81
84
} ) ;
82
85
case RESET : {
83
- const rejectors = { ...this . rejectors } ;
86
+ // take snapshot of rejectors at this point in time
87
+ // we must use Array.from since iteration does not freeze state at this point in time
88
+ const fetches = Array . from ( this . fetching . values ( ) ) ;
84
89
85
90
this . clearAll ( ) ;
86
91
return next ( action ) . then ( ( ) => {
87
92
// there could be external listeners to the promise
88
93
// this must happen after commit so our own rejector knows not to dispatch an error based on this
89
- for ( const k in rejectors ) {
90
- rejectors [ k ] ( new ResetError ( ) ) ;
94
+ for ( const { reject } of fetches ) {
95
+ reject ( new ResetError ( ) ) ;
91
96
}
92
97
} ) ;
93
98
}
@@ -112,28 +117,29 @@ export default class NetworkManager implements Manager {
112
117
/** Used by DevtoolsManager to determine whether to log an action */
113
118
skipLogging ( action : ActionTypes ) {
114
119
/* istanbul ignore next */
115
- return action . type === FETCH && action . key in this . fetched ;
120
+ return action . type === FETCH && this . fetching . has ( action . key ) ;
116
121
}
117
122
118
123
allSettled ( ) {
119
- const fetches = Object . values ( this . fetched ) ;
120
- if ( fetches . length ) return Promise . allSettled ( fetches ) ;
124
+ if ( this . fetching . size )
125
+ return Promise . allSettled (
126
+ this . fetching . values ( ) . map ( ( { promise } ) => promise ) ,
127
+ ) ;
121
128
}
122
129
123
130
/** Clear all promise state */
124
131
protected clearAll ( ) {
125
- for ( const k in this . rejectors ) {
132
+ for ( const k of this . fetching . keys ( ) ) {
126
133
this . clear ( k ) ;
127
134
}
128
135
}
129
136
130
137
/** Clear promise state for a given key */
131
138
protected clear ( key : string ) {
132
- this . fetched [ key ] . catch ( ( ) => { } ) ;
133
- delete this . resolvers [ key ] ;
134
- delete this . rejectors [ key ] ;
135
- delete this . fetched [ key ] ;
136
- delete this . fetchedAt [ key ] ;
139
+ if ( this . fetching . has ( key ) ) {
140
+ ( this . fetching . get ( key ) as FetchingMeta ) . promise . catch ( ( ) => { } ) ;
141
+ this . fetching . delete ( key ) ;
142
+ }
137
143
}
138
144
139
145
protected getLastReset ( ) {
@@ -226,14 +232,14 @@ export default class NetworkManager implements Manager {
226
232
*/
227
233
protected handleSet ( action : SetResponseAction ) {
228
234
// this can still turn out to be untrue since this is async
229
- if ( action . key in this . fetched ) {
230
- let promiseHandler : ( value ?: any ) => void ;
235
+ if ( this . fetching . has ( action . key ) ) {
236
+ const { reject , resolve } = this . fetching . get ( action . key ) as FetchingMeta ;
231
237
if ( action . error ) {
232
- promiseHandler = this . rejectors [ action . key ] ;
238
+ reject ( action . response ) ;
233
239
} else {
234
- promiseHandler = this . resolvers [ action . key ] ;
240
+ resolve ( action . response ) ;
235
241
}
236
- promiseHandler ( action . response ) ;
242
+
237
243
// since we're resolved we no longer need to keep track of this promise
238
244
this . clear ( action . key ) ;
239
245
}
@@ -253,19 +259,23 @@ export default class NetworkManager implements Manager {
253
259
key : string ,
254
260
fetch : ( ) => Promise < any > ,
255
261
fetchedAt : number ,
256
- ) {
262
+ ) : Promise < any > {
257
263
const lastReset = this . getLastReset ( ) ;
264
+ let fetchMeta = this . fetching . get ( key ) ;
265
+
258
266
// we're already fetching so reuse the promise
259
267
// fetches after reset do not count
260
- if ( key in this . fetched && this . fetchedAt [ key ] > lastReset ) {
261
- return this . fetched [ key ] ;
268
+ if ( fetchMeta && fetchMeta . fetchedAt > lastReset ) {
269
+ return fetchMeta . promise ;
262
270
}
263
271
264
- this . fetched [ key ] = new Promise ( ( resolve , reject ) => {
265
- this . resolvers [ key ] = resolve ;
266
- this . rejectors [ key ] = reject ;
272
+ fetchMeta = { fetchedAt } as FetchingMeta ;
273
+ fetchMeta . promise = new Promise ( ( resolve , reject ) => {
274
+ fetchMeta . resolve = resolve ;
275
+ fetchMeta . reject = reject ;
267
276
} ) ;
268
- this . fetchedAt [ key ] = fetchedAt ;
277
+
278
+ this . fetching . set ( key , fetchMeta ) ;
269
279
270
280
this . idleCallback (
271
281
( ) => {
@@ -277,7 +287,7 @@ export default class NetworkManager implements Manager {
277
287
{ timeout : 500 } ,
278
288
) ;
279
289
280
- return this . fetched [ key ] ;
290
+ return fetchMeta . promise ;
281
291
}
282
292
283
293
/** Calls the callback when client is not 'busy' with high priority interaction tasks
0 commit comments