angular6 cache interceptor

0

Witam, bawię się w cachowanie danych słownikowych i natrafiłem na pewien problem, wyjaśnię od razu, że często komponenty pobierają słownik kilkukrotnie (np. poprzez pipe na htmlu).
Mój problem pojawia się za pierwszym pobraniem słowników, bo dopiero za 6 wywołaniem słownika zapisuje się on do cache (na networku request puszczony 6 razy). Zastanawiam się czy to nie jest problem asynchroniczności, bo po tym pierwszym załadowaniu strony cache działa prawidłowo.

export class CacheInterceptor implements HttpInterceptor {

    constructor(private cache: RequestCache) {}

    intercept(req: HttpRequest<any>, next: HttpHandler) {
        const cachedResponse = this.cache.get(req);
        return cachedResponse ? of(cachedResponse) : this.sendRequest(req, next, this.cache);
    }

funkcja get nie znajduje ładowanego po raz pierwszy cache więc idzie do funkcji sendRequest:

    sendRequest(
        req: HttpRequest<any>,
        next: HttpHandler,
        cache: RequestCache): Observable<HttpEvent<any>> {
        return next.handle(req).pipe(
            tap(event => {
                if (event instanceof HttpResponse && this.isCachable(req.url)) {
                    cache.put(req, event);
                }
            })
        );
    }

Jak sprawić, żeby już za pierwszym razem słownik został zapisany do cache aby nawet w pierwszym przeładowaniu nie pobierać kilkukrotnie tego samego?

0

Naprawdę nikt nic? ;/ Ogólnie doszedłem do tego, że gdybym cache robił dla każdego osobnego requesta, to wystarczyłoby czekać subscribem na odpowiedź. W tym przypadku jednak to nie jest tak łatwe, a zależy mi na globalnym rozwiązaniu. Nie wiem w jaki sposób wymusić w tej linijce:

 return cachedResponse ? of(cachedResponse) : this.sendRequest(req, next, this.cache);

aby czekać na zakończenie całego requesta w sendRequest

0

zapoznaj się z async / await - powinno pomóc

1

Z tego co się orientuję to w Angularze używanie async / await to nie jest najczęściej spotykana praktyka. Nie bardzo rozumiem co chcesz osiągnać,

Aby nawet w pierwszym przeładowaniu nie pobierać kilkukrotnie tego samego>

Podobny temat pojawił sie na stacku, tutaj akurat gość miał coś źle w providers https://stackoverflow.com/questions/49039638/attempting-to-cache-httpclient-request-in-angular-but-seeing-unresolved-variable.

Druga sprawa to

wyjaśnię od razu, że często komponenty pobierają słownik kilkukrotnie (np. poprzez pipe na htmlu).>

Kilkukrotnie subskrybujesz po te same dane w jednym widoku za pomocą pipe?

2

No dobra będę pisał z pamięci bo nie zmontowałeś żadnego przykładowego REPLa.

Twój problem polega na tym że w komponentach masz przykładowo 6 pipe'ów. Każdy w tym samym momencie sprawdza, czy dane są w cache (nie ma ich) więc wykonuje request HTTP.

Rozwiązanie jest następujące: musisz mieć jakieś miejsce w którym trzymasz flagi runningRequest dla każdego endpointa. Najlepszy będzie Set.

    intercept(req: HttpRequest<any>, next: HttpHandler) {
        if (this.runningRequests.has(req.url)) {
          return new Observable(...);
        }

        const cachedResponse = this.cache.get(req);
        return cachedResponse ? of(cachedResponse) : this.sendRequest(req, next, this.cache);
    }

w metodzie sendRequest dodajesz linijki

    sendRequest(
        req: HttpRequest<any>,
        next: HttpHandler,
        cache: RequestCache): Observable<HttpEvent<any>> {
        this.runningRequests.add(req.url);
        return next.handle(req).pipe(
            tap(event => {
                if (event instanceof HttpResponse && this.isCachable(req.url)) {
                    cache.put(req, event);
                    this.runningRequests.delete(req.url);
                }
            })
        );
    }

To sprawi, że nigdy nie będą wykonane 2 requesty do jednego endpointa w tym samym czasie.

Pozostaje jeszcze napisać strumień, który zorientuje się, że danych których chwilę temu nie było, właśnie się pojawiły.

    intercept(req: HttpRequest<any>, next: HttpHandler) {
        if (this.runningRequests.has(req.url)) {
          return this.requestSuccessSubjects.get(req.url).pipe(first(), map(() => this.cache.get(req)));
        }

        const cachedResponse = this.cache.get(req);
        return cachedResponse ? of(cachedResponse) : this.sendRequest(req, next, this.cache);
    }

    sendRequest(
        req: HttpRequest<any>,
        next: HttpHandler,
        cache: RequestCache): Observable<HttpEvent<any>> {
        this.runningRequests.add(req.url);
        return next.handle(req).pipe(
            tap(event => {
                if (event instanceof HttpResponse && this.isCachable(req.url)) {
                    cache.put(req, event);
                    this.runningRequests.delete(req.url);
                    this.requestSuccessSubjects.get(req.url).next();
                }
            })
        );
    }

requestSuccessSubjects to Map złożony z Subject. Przemyśl też edge casy. Pewnie jakieś się znajdą.

Pojebane? Nazywam się mchoinka i witam w RxJS.

Tak poza tym, zrobiłbym domenowy serwis zamiast interceptora.

1 użytkowników online, w tym zalogowanych: 0, gości: 1