@@ -5,7 +5,6 @@ import type {
55 FetchAction ,
66 Manager ,
77 ActionTypes ,
8- MiddlewareAPI ,
98 Middleware ,
109 SetResponseAction ,
1110} from '../types.js' ;
@@ -18,6 +17,13 @@ export class ResetError extends Error {
1817 }
1918}
2019
20+ export interface FetchingMeta {
21+ promise : Promise < any > ;
22+ resolve : ( value ?: any ) => void ;
23+ reject : ( value ?: any ) => void ;
24+ fetchedAt : number ;
25+ }
26+
2127/** Handles all async network dispatches
2228 *
2329 * Dedupes concurrent requests by keeping track of all fetches in flight
@@ -28,10 +34,7 @@ export class ResetError extends Error {
2834 * @see https://dataclient.io/docs/api/NetworkManager
2935 */
3036export 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 ( ) ;
3538 declare readonly dataExpiryLength : number ;
3639 declare readonly errorExpiryLength : number ;
3740 protected controller : Controller = new Controller ( ) ;
@@ -61,7 +64,7 @@ export default class NetworkManager implements Manager {
6164 case SET_RESPONSE :
6265 // only set after new state is computed
6366 return next ( action ) . then ( ( ) => {
64- if ( action . key in this . fetched ) {
67+ if ( this . fetching . has ( action . key ) ) {
6568 // Note: meta *must* be set by reducer so this should be safe
6669 const error = controller . getState ( ) . meta [ action . key ] ?. error ;
6770 // processing errors result in state meta having error, so we should reject the promise
@@ -80,14 +83,16 @@ export default class NetworkManager implements Manager {
8083 }
8184 } ) ;
8285 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 ( ) ) ;
8489
8590 this . clearAll ( ) ;
8691 return next ( action ) . then ( ( ) => {
8792 // there could be external listeners to the promise
8893 // 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 ( ) ) ;
9196 }
9297 } ) ;
9398 }
@@ -112,28 +117,29 @@ export default class NetworkManager implements Manager {
112117 /** Used by DevtoolsManager to determine whether to log an action */
113118 skipLogging ( action : ActionTypes ) {
114119 /* istanbul ignore next */
115- return action . type === FETCH && action . key in this . fetched ;
120+ return action . type === FETCH && this . fetching . has ( action . key ) ;
116121 }
117122
118123 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+ ) ;
121128 }
122129
123130 /** Clear all promise state */
124131 protected clearAll ( ) {
125- for ( const k in this . rejectors ) {
132+ for ( const k of this . fetching . keys ( ) ) {
126133 this . clear ( k ) ;
127134 }
128135 }
129136
130137 /** Clear promise state for a given key */
131138 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+ }
137143 }
138144
139145 protected getLastReset ( ) {
@@ -226,14 +232,14 @@ export default class NetworkManager implements Manager {
226232 */
227233 protected handleSet ( action : SetResponseAction ) {
228234 // 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 ;
231237 if ( action . error ) {
232- promiseHandler = this . rejectors [ action . key ] ;
238+ reject ( action . response ) ;
233239 } else {
234- promiseHandler = this . resolvers [ action . key ] ;
240+ resolve ( action . response ) ;
235241 }
236- promiseHandler ( action . response ) ;
242+
237243 // since we're resolved we no longer need to keep track of this promise
238244 this . clear ( action . key ) ;
239245 }
@@ -253,19 +259,18 @@ export default class NetworkManager implements Manager {
253259 key : string ,
254260 fetch : ( ) => Promise < any > ,
255261 fetchedAt : number ,
256- ) {
262+ ) : Promise < any > {
257263 const lastReset = this . getLastReset ( ) ;
264+ let fetchMeta = this . fetching . get ( key ) ;
265+
258266 // we're already fetching so reuse the promise
259267 // 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 ;
262270 }
263271
264- this . fetched [ key ] = new Promise ( ( resolve , reject ) => {
265- this . resolvers [ key ] = resolve ;
266- this . rejectors [ key ] = reject ;
267- } ) ;
268- this . fetchedAt [ key ] = fetchedAt ;
272+ fetchMeta = newFetchMeta ( fetchedAt ) ;
273+ this . fetching . set ( key , fetchMeta ) ;
269274
270275 this . idleCallback (
271276 ( ) => {
@@ -277,7 +282,7 @@ export default class NetworkManager implements Manager {
277282 { timeout : 500 } ,
278283 ) ;
279284
280- return this . fetched [ key ] ;
285+ return fetchMeta . promise ;
281286 }
282287
283288 /** Calls the callback when client is not 'busy' with high priority interaction tasks
@@ -291,3 +296,12 @@ export default class NetworkManager implements Manager {
291296 callback ( ) ;
292297 }
293298}
299+
300+ function newFetchMeta ( fetchedAt : number ) : FetchingMeta {
301+ const fetchMeta = { fetchedAt } as FetchingMeta ;
302+ fetchMeta . promise = new Promise ( ( resolve , reject ) => {
303+ fetchMeta . resolve = resolve ;
304+ fetchMeta . reject = reject ;
305+ } ) ;
306+ return fetchMeta ;
307+ }
0 commit comments