Angular HttpInterceptor 通过共享可观察对象来缓存并行请求

Sim*_*gro 5 javascript rxjs typescript angular

我想通过共享可观察对象来缓存 HTTP 并行请求,并将响应缓存在 Map 对象中。

在线演示

缓存interceptor.service.ts

import { HttpEvent, HttpHandler, HttpInterceptor, HttpRequest, HttpResponse } from '@angular/common/http';
import { Injectable } from '@angular/core';
import { Observable, of } from 'rxjs';
import { tap, finalize, share } from 'rxjs/operators';


@Injectable()
export class CachingInterceptorService implements HttpInterceptor {

  public readonly store = new Map<string, HttpResponse<any>>();
  public readonly queue = new Map<string, Observable<HttpEvent<any>>>();

  constructor() {}

  intercept(req: HttpRequest<any>, next: HttpHandler): Observable<HttpEvent<any>> {

    // Don't cache if it's not cacheable
    if ( req.method !== 'GET' ) {
      return next.handle(req);
    }

    // Checked if there is pending response for this request
    const cachedObservable: Observable<HttpEvent<any>> = this.queue.get(req.urlWithParams);
    if ( cachedObservable ) {
      console.info('Observable cached');
      return cachedObservable;
    }

    // Checked if there is cached response for this request
    const cachedResponse: HttpResponse<any> = this.store.get(req.urlWithParams);
    if (cachedResponse) {
      console.info('Response cached');
      return of(cachedResponse.clone());
    }

    // If the request of going through for first time
    // then let the request proceed and cache the response
    console.info('Request execute');
    const shared = next.handle(req).pipe(
      tap(event => {
        if (event instanceof HttpResponse) {
          console.info('Response reached');
          this.store.set(req.urlWithParams, event.clone());
        }
      }),
      finalize(() => {
        // delete pending request
        this.queue.delete(req.urlWithParams);
      }),
      share()
    );

    // add pending request to queue for cache parallell request
    this.queue.set(req.urlWithParams, shared);

    return shared;
  }
}
Run Code Online (Sandbox Code Playgroud)

这种可观察缓存的实现是否正确?

我有些怀疑:如果 observable 被删除到请求的最终确定中并且有些人已经订阅了会发生什么?

旁注:这只是一个例子,并没有实现缓存过期/失效。

She*_*iny 5

您的实现工作正常,但我认为它可以变得更简单。

import { HttpEvent, HttpHandler, HttpInterceptor, HttpRequest, HttpResponse } from '@angular/common/http';
import { Injectable } from '@angular/core';
import { Observable } from 'rxjs';
import { shareReplay, first, filter } from 'rxjs/operators';


@Injectable()
export class CachingInterceptorService implements HttpInterceptor {

  public readonly store: Record<string, Observable<HttpEvent<any>>> = {};

  constructor() {}

  intercept(req: HttpRequest<any>, next: HttpHandler): Observable<HttpEvent<any>> {

    // Don't cache if it's not cacheable
    if ( req.method !== 'GET' ) {
      return next.handle(req);
    }

    // Check if observable is in cache, otherwise call next.handle
    const cachedObservable = this.store[req.urlWithParams] ||
      ( this.store[req.urlWithParams] = next.handle(req).pipe(
          // Filter since we are interested in caching the response only, not progress events
          filter((res) => res instanceof HttpResponse ),
          // Share replay will cache the response
          shareReplay(1),
      ));
    // pipe first() to cause the observable to complete after it emits the response
    // This mimics the behaviour of Observables returned by Angular's httpClient.get() 
    // And also makes toPromise work since toPromise will wait until the observable completes.
    return cachedObservable.pipe(first());
  }
}
Run Code Online (Sandbox Code Playgroud)

检查我的Stackblitz 叉子