angular6 cache interceptor

angular6 cache interceptor
Kondziowsky
  • Rejestracja:około 6 lat
  • Ostatnio:ponad 2 lata
  • Postów:219
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.

Kopiuj
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:

Kopiuj
    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?

Kondziowsky
  • Rejestracja:około 6 lat
  • Ostatnio:ponad 2 lata
  • Postów:219
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:

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

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

AD
  • Rejestracja:ponad 11 lat
  • Ostatnio:6 dni
  • Postów:481
0

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

Kondziowsky
no właśnie próbuję to zrobić async/await ale strasznie mi to opornie idzie w tym interceptorze. Np. gdy zrobiłem await this.sendRequest(a,b) i async sendRequest(właściwa funkcja z try {}) to dostaję strasznie dziwny błąd 'Module parse failed: The keyword 'yield' is reserved (63:57)'. Przy buildzie pluje się o finally. Dodaje finally i catch do try, to wyskakuje mi ''await' expression is only allowed within an async function.'. I tak się bawię :D
marcio
bo async ma isc tam gdzie oczekujesz uzyc await czuli przed deklaracja metody intercept
Kondziowsky
To wciąż nie to - mam błąd 'property 'intercept' in type 'CacheInterceptor' is not assignable to the same property in base type 'HttpInterceptor'.' a dalej 'type promise<Observable<HttpEvent<any>>> is not assignable to type Observable<HttpEvent<any>>' :( Jeśli ktoś ma jeszcze jakiś pomysł- będę wdzięczny, jeśli nie to pewnie w weekend przysiądę.
ST
  • Rejestracja:prawie 10 lat
  • Ostatnio:ponad 5 lat
  • Postów:51
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?

edytowany 5x, ostatnio: Steff
Kondziowsky
Wiem, że trochę to chaotycznie opisałem, ale po prostu jak niżej - mam w komponentach 6 pipów i sprawdzają one czy mam już pobrany słownik czy muszą go pobrać. W każdym razie dzięki za zainteresowanie
MC
  • Rejestracja:około 6 lat
  • Ostatnio:ponad 5 lat
  • Postów:3
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.

Kopiuj
    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

Kopiuj
    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.

Kopiuj
    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.

Kondziowsky
o kurde, dzięki wielkie, bo już traciłem nadzieję na jakiekolwiek rozwiązanie :D mimo, że rozwiązanie dostałem to dopytam o to, co dodałeś na końcu - o co chodzi konkretnie z edge case i domenowym serwisem? Przybliżysz coś tu / na pw? Oczywiście sam też na ten temat poszukam :P
MC
Edge case: no ogółem ten kod pewnie się czasami wywali. Np. jak poleci błąd HTTP. Będziesz go musiał rozbudować. A jak? Nie wiem, bo pisałem to na szybko Domenowy serwis: zamiast do twoich pipów injectowac HttpClient zrobiłbym, np. Dictionary1Service i tam trzymał strumień używany w Pipe. Za pierwszy razem jak ktoś poprosi o ten strumień wykonujesz zapytanie HTTP a jak się wykona, wrzucasz ja na wspomniany strumień. Dodaj operator shareReplay(1) i masz proste cache w jednej linijce. Domena = Posty, Użytkownicy, Kraje... Tzn. określona część modelu aplikacji Pozdro

Zarejestruj się i dołącz do największej społeczności programistów w Polsce.

Otrzymaj wsparcie, dziel się wiedzą i rozwijaj swoje umiejętności z najlepszymi.