Thank you, in the meantime I've came up with an upgrade to my cache service, but since it's seems quite over-engineering, I'll probably accept other answer. Still, I believe the below code might be helpful to someone in the future.
Generally, the first request of with the specific type (urlWithParams
) is stored in pending
. Until this request is finished, further requests of the same type are stored in waiting
with corresponding new, empty subject and observable from this subject is returned. When the pending request finishes, it sends the correct HttpResponse to waiting subjects of this type.
@Injectable()
export class HttpCacheInterceptor implements HttpInterceptor {
constructor(private cache: CoreHttpCacheService) {}
intercept(req: HttpRequest<any>, next: HttpHandler): Observable<HttpEvent<any>> {
if( req.method !== 'GET' && req.method !== 'OPTIONS' ) {
this.cache.clear();
}
if( req.method !== 'GET' || req.headers.get( CC.NO_CACHE ) ) {
return next.handle(req);
}
const cachedResponse: HttpResponse<any> = this.cache.get(req.urlWithParams);
if( this.cache.pending.has( req.urlWithParams ) ) {
const sbj = new Subject<HttpEvent<any>>();
this.cache.waiting.push( {r: req.urlWithParams, s: sbj} );
return sbj.asObservable();
} else {
if( cachedResponse ) {
return of( cachedResponse.clone() );
} else {
if( ! this.cache.pending.has( req.urlWithParams ) ) {
this.cache.pending.add( req.urlWithParams );
}
return next.handle(req).pipe(
tap(stateEvent => {
if(stateEvent instanceof HttpResponse) {
this.cache.set(req.urlWithParams, stateEvent.clone());
let i = this.cache.waiting.length;
while( i-- ) {
if( this.cache.waiting[i].r === req.urlWithParams ) {
this.cache.waiting[ i ].s.next( stateEvent.clone() );
this.cache.waiting.splice( i, 1 );
}
}
this.cache.pending.delete( req.urlWithParams );
}
})
);
}
}
}
}
@Injectable({
providedIn: 'root'
})
export class CoreHttpCacheService {
pending = new Set< string >();
waiting: {r: string, s: Subject<HttpEvent<any>> }[] = [];
private cache: Map< string, HttpResponse<any> > = new Map();
private timers = new Map< string, NodeJS.Timer >();
get( key: string ) {
return this.cache.get( key );
}
set( key: string, v: any ) {
this.timers.set( key, setTimeout( () => this.clear( key ), Constants.TIME_100SEC ) );
return this.cache.set( key, v );
}
clear( key?: string ) {
if( key !== undefined ) {
this.cache.delete( key );
} else {
this.cache.clear();
}
}
}
This example will fail on HTTP error, compares requests only by urlWithParams and CoreHttpCacheService fields are public, so be careful if you'd like to use it.