Korzystając z protokołu HTTP, wywołujemy metodę, która wykonuje połączenie sieciowe i zwraca obserwowalny http:
getCustomer() {
return this.http.get('/someUrl').map(res => res.json());
}
Jeśli weźmiemy to pod uwagę i dodamy do niej wielu subskrybentów:
let network$ = getCustomer();
let subscriber1 = network$.subscribe(...);
let subscriber2 = network$.subscribe(...);
Chcemy mieć pewność, że nie spowoduje to wielu żądań sieciowych.
Może się to wydawać niezwykłym scenariuszem, ale jest tak naprawdę dość powszechny: na przykład, jeśli wywołujący subskrybuje obserwowalny, aby wyświetlić komunikat o błędzie i przekazuje go do szablonu za pomocą potoku asynchronicznego, mamy już dwóch subskrybentów.
Jak to zrobić w RxJs 5?
Mianowicie wydaje się to działać dobrze:
getCustomer() {
return this.http.get('/someUrl').map(res => res.json()).share();
}
Ale czy jest to idiomatyczny sposób robienia tego w RxJs 5, czy zamiast tego powinniśmy zrobić coś innego?
Uwaga: Zgodnie z kątowym 5 Nowy HttpClient
The .map(res => res.json())
część we wszystkich przykładach jest teraz bezużyteczne, jako wynik JSON jest teraz zakłada domyślnie.
źródło
Odpowiedzi:
Buforuj dane i jeśli są dostępne w pamięci podręcznej, zwróć to, w przeciwnym razie prześlij żądanie HTTP.
Przykład plunkera
Ten artykuł https://blog.thoughtram.io/angular/2018/03/05/advanced-caching-with-rxjs.html to świetne wyjaśnienie dotyczące sposobu buforowania
shareReplay
.źródło
do()
w przeciwieństwie domap()
nie modyfikuje zdarzenia. Możesz również użyć,map()
ale musisz upewnić się, że na końcu wywołania zwrotnego zwracana jest poprawna wartość..subscribe()
to, nie potrzebuje wartości, możesz to zrobić, ponieważ może się ona po prostu dostaćnull
(w zależności od tego, cothis.extractData
zwraca), ale IMHO nie wyraża dobrze intencji kodu.this.extraData
kończy sięextraData() { if(foo) { doSomething();}}
inaczej, zwracany jest wynik ostatniego wyrażenia, które może nie być tym, czego chcesz.if (this.observable) { return this.observable; } else { this.observable = this.http.get(url) .map(res => res.json().data); return this.observable; }
Według sugestii @Cristian jest to jeden ze sposobów, który działa dobrze dla obserwowalnych HTTP, które emitują tylko raz, a następnie wykonują:
źródło
share
operator może być rozsądnym wyborem (choć w przypadku niektórych nieprzyjemnych przypadków krawędziowych). Dla głębokiego nurkowania dyskusji na temat opcji zobaczyć komentarze sekcję w tym blogu: blog.jhades.org/...publishLast().refCount()
nie można anulować udostępniania obserwowalnego źródła, nie można go anulować, ale po anulowaniu wszystkich subskrypcji zwróconego obserwowalnegorefCount
, efektem netto jest obserwowanie źródła, które nie będzie subskrybowane, anulując je, jeśli to w miejscu „inflight”AKTUALIZACJA: Ben Lesh mówi, że w następnej wersji mniejszej po 5.2.0 będziesz mógł po prostu wywołać shareReplay (), aby naprawdę buforować.
POPRZEDNIO.....
Po pierwsze, nie używaj share () ani publiceplay (1) .refCount (), są one takie same, a problemem jest to, że udostępnia tylko wtedy, gdy połączenia są nawiązywane, gdy obserwowalne jest aktywne, jeśli łączysz się po jego zakończeniu. , ponownie tworzy nowy obserwowalny, tłumaczenie, a nie buforowanie.
Birowski podał powyższe właściwe rozwiązanie, którym jest użycie ReplaySubject. ReplaySubject zbuforuje wartości, które mu podasz (bufferSize) w naszym przypadku 1. Nie utworzy nowego obserwowalnego, takiego jak share (), gdy refCount osiągnie zero i nawiążesz nowe połączenie, co jest właściwym zachowaniem do buforowania.
Oto funkcja wielokrotnego użytku
Oto jak z niego korzystać
Poniżej znajduje się bardziej zaawansowana wersja funkcji pamięci podręcznej. Ta umożliwia własną tabelę wyszukiwania + możliwość zapewnienia niestandardowej tabeli wyszukiwania. W ten sposób nie musisz sprawdzać this._cache jak w powyższym przykładzie. Zauważ również, że zamiast przekazywać obserwowalny jako pierwszy argument, przekazujesz funkcję, która zwraca obserwowalne, to dlatego, że Http Angulara wykonuje się od razu, więc zwracając leniwie wykonaną funkcję, możemy zdecydować, aby nie wywoływać jej, jeśli jest już w nasza pamięć podręczna.
Stosowanie:
źródło
const data$ = this._http.get('url').pipe(cacheable()); /*1st subscribe*/ data$.subscribe(); /*2nd subscribe*/ data$.subscribe();
? Zachowuje się więc bardziej jak każdy inny operator ...rxjs 5.4.0 ma nową metodę shareReplay .
Autor wyraźnie mówi „idealny do obsługi takich rzeczy, jak buforowanie wyników AJAX”
rxjs PR # 2443 feat (shareReplay): dodaje
shareReplay
wariantpublishReplay
źródło
zgodnie z tym artykułem
więc w środku, jeśli po prostu dołączą instrukcje
do
.map(...)
źródło
Wersja rxjs 5.4.0 (2017-05-09) dodaje obsługę shareReplay .
Możesz łatwo zmodyfikować usługę kątową, aby z niej skorzystać i zwrócić obserwowalny wynik z pamięci podręcznej, który spowoduje, że połączenie http zostanie wykonane tylko raz (zakładając, że pierwsze połączenie zakończyło się powodzeniem).
Przykładowa usługa kątowa
Oto bardzo prosta obsługa klienta, z której korzysta
shareReplay
.customer.service.ts
Zauważ, że przypisanie do konstruktora można przenieść do metody,
getCustomers
ale ponieważ obserwowalne elementy zwracaneHttpClient
są „zimne”, robienie tego w konstruktorze jest dopuszczalne, ponieważ wywołanie http zostanie wykonane tylko za pierwszym razemsubscribe
.Zakłada się tutaj również, że początkowe zwrócone dane nie stają się przestarzałe w czasie życia instancji aplikacji.
źródło
Zagrałem gwiazdką w pytanie, ale spróbuję spróbować.
Oto dowód :)
Jest tylko jedna na wynos:
getCustomer().subscribe(customer$)
Nie subskrybujemy odpowiedzi interfejsu API
getCustomer()
, subskrybujemy replaySubject, który jest możliwy do zaobserwowania, który może również subskrybować inny obserwowalny i (i to jest ważne) utrzymać ostatnią emitowaną wartość i ponownie opublikować dowolną z nich (ReplaySubject's ) subskrybentów.źródło
Znalazłem sposób na przechowanie wyniku HTTP get w sessionStorage i wykorzystanie go do sesji, aby nigdy więcej nie zadzwonił do serwera.
Użyłem go do wywołania API github, aby uniknąć ograniczenia użycia.
Do Twojej wiadomości limit sessionStorage to 5M (lub 4,75M). Dlatego nie należy go tak używać w przypadku dużego zestawu danych.
------ edytuj -------------
Jeśli chcesz odświeżyć dane za pomocą F5, który wykorzystuje dane pamięci zamiast sessionStorage;
źródło
sessionStorage
wskazuje, użyłbym tego tylko dla danych, które powinny być spójne dla całej sesji.getCustomer
w swoim przykładzie. ;) Więc chciałem tylko ostrzec ppl, że może nie dostrzegać ryzyka :)Wybrana implementacja będzie zależała od tego, czy chcesz anulować subskrypcję (), aby anulować żądanie HTTP, czy nie.
W każdym razie dekoratory TypeScript są dobrym sposobem na ustandaryzowanie zachowania. Oto ten, który napisałem:
Definicja dekoratora:
źródło
Property 'connect' does not exist on type '{}'.
z liniireturnValue.connect();
. Czy możesz rozwinąć?Dane odpowiedzi HTTP w pamięci podręcznej przy użyciu Rxjs Observer / Observable + Caching + Subskrypcja
Zobacz kod poniżej
* zastrzeżenie: Jestem nowy w rxjs, więc pamiętaj, że mogę niewłaściwie używać podejścia obserwowalnego / obserwatora. Moje rozwiązanie jest wyłącznie konglomeratem innych rozwiązań, które znalazłem, i jest konsekwencją tego, że nie znalazłem prostego, dobrze udokumentowanego rozwiązania. W ten sposób dostarczam moje kompletne rozwiązanie kodu (jak chciałbym to znaleźć) w nadziei, że pomoże innym.
* Uwaga, to podejście jest luźno oparte na GoogleFirebaseObservables. Niestety brakuje mi odpowiedniego doświadczenia / czasu, aby powtórzyć to, co zrobili pod maską. Ale poniżej przedstawiono uproszczony sposób zapewnienia asynchronicznego dostępu do niektórych danych, które mogą być buforowane.
Sytuacja : Zadaniem komponentu „lista produktów” jest wyświetlanie listy produktów. Witryna to jednostronna aplikacja internetowa z kilkoma przyciskami menu, które będą „filtrować” produkty wyświetlane na stronie.
Rozwiązanie : składnik „subskrybuje” metodę usługi. Metoda usługi zwraca tablicę obiektów produktu, do których komponent uzyskuje dostęp poprzez wywołanie zwrotne subskrypcji. Metoda usługi zawija swoją aktywność w nowo utworzonym Observer i zwraca obserwatora. Wewnątrz tego obserwatora wyszukuje dane w pamięci podręcznej i przekazuje je subskrybentowi (komponentowi) i zwraca. W przeciwnym razie wywołuje połączenie http w celu pobrania danych, subskrybuje odpowiedź, w której można przetworzyć te dane (np. Zmapować dane do własnego modelu), a następnie przekazać dane subskrybentowi.
Kod
product-list.component.ts
product.service.ts
product.ts (model)
Oto próbka danych wyjściowych, które widzę, gdy ładuję stronę w Chrome. Zauważ, że przy początkowym ładowaniu produkty są pobierane z http (połączenie z moją usługą odpoczynku węzła, która działa lokalnie na porcie 3000). Kiedy następnie klikam, aby przejść do widoku „filtrowanego” produktów, produkty znajdują się w pamięci podręcznej.
Mój dziennik Chrome (konsola):
... [kliknął przycisk menu, aby przefiltrować produkty] ...
Wniosek: Jest to najprostszy (jak dotąd) sposób zaimplementowania buforowanych danych odpowiedzi HTTP. W mojej aplikacji kątowej za każdym razem, gdy przechodzę do innego widoku produktów, składnik listy produktów ładuje się ponownie. ProductService wydaje się być współużytkowanym wystąpieniem, więc lokalna pamięć podręczna „products: Product []” w ProductService jest zachowywana podczas nawigacji, a kolejne wywołania „GetProducts ()” zwracają buforowaną wartość. I ostatnia uwaga, przeczytałem komentarze o tym, jak obserwowalne / subskrypcje muszą być zamknięte, kiedy skończysz, aby zapobiec „wyciekom pamięci”. Nie zamieściłem tego tutaj, ale warto o tym pamiętać.
źródło
Zakładam, że @ ngx-cache / core może być użyteczny do utrzymania funkcji buforowania dla połączeń HTTP, szczególnie jeśli połączenie HTTP jest wykonywane zarówno w przeglądarce, jak i na serwerze platformie .
Powiedzmy, że mamy następującą metodę:
Można użyć
Cached
dekorator z @ NGX-cache / rdzeń do przechowywania wartości zwracane z metody podejmowania połączenia HTTP Podcache storage
( może być konfigurowalny, proszę sprawdzić wdrażania na NG-nasiennej / uniwersalny ) - Prawo w pierwszym wykonaniu. Przy następnym wywołaniu metody (bez względu na platformę przeglądarki lub serwera ) wartość jest pobierana z .storage
cache storage
Istnieje również możliwość stosowanie metod buforowania (
has
,get
,set
) z wykorzystaniem API buforowania .anyclass.ts
Oto lista pakietów, zarówno do buforowania po stronie klienta, jak i serwera:
źródło
rxjs 5.3.0
Nie byłem zadowolony
.map(myFunction).publishReplay(1).refCount()
W przypadku wielu subskrybentów w niektórych przypadkach
.map()
wykonuje sięmyFunction
dwukrotnie (spodziewam się, że wykona się tylko raz). Jedna poprawka wydaje się byćpublishReplay(1).refCount().take(1)
Inną rzeczą, którą możesz zrobić, to po prostu nie używać
refCount()
i natychmiast sprawić, by Observable był gorący:Spowoduje to uruchomienie żądania HTTP niezależnie od subskrybentów. Nie jestem pewien, czy anulowanie subskrypcji przed zakończeniem HTTP GET ją anuluje, czy nie.
źródło
Moim osobistym ulubionym jest korzystanie z
async
metod połączeń, które wysyłają żądania sieciowe. Same metody nie zwracają wartości, zamiast tego aktualizująBehaviorSubject
usługę ramach tej samej usługi, którą subskrybują komponenty.Teraz po co używać
BehaviorSubject
zamiast zamiastObservable
? Ponieważ,onnext
.getValue()
metody.Przykład:
customer.service.ts
Następnie, gdziekolwiek jest to wymagane, możemy po prostu subskrybować
customers$
.A może chcesz użyć go bezpośrednio w szablonie
Zatem teraz, dopóki nie wykonasz kolejnego połączenia
getCustomers
, dane są przechowywane wcustomers$
BehaviorSubject.Co jeśli chcesz odświeżyć te dane? po prostu zadzwoń do
getCustomers()
Korzystając z tej metody, nie musimy jawnie zatrzymywać danych między kolejnymi połączeniami sieciowymi, ponieważ są one obsługiwane przez
BehaviorSubject
.PS: Zazwyczaj, gdy element ulega zniszczeniu, dobrą praktyką jest pozbycie się subskrypcji, w tym celu możesz użyć metody sugerowanej w tej odpowiedzi.
źródło
Świetne odpowiedzi.
Lub możesz to zrobić:
To jest z najnowszej wersji rxjs. Korzystam z wersji RxJS 5.5.7
źródło
Po prostu wywołaj share () po mapie i przed subskrypcją .
W moim przypadku mam usługę ogólną (RestClientService.ts), która wykonuje wywołanie reszty, wyodrębnia dane, sprawdza błędy i wraca do obserwowalności do konkretnej usługi wdrożeniowej (np .: ContractClientService.ts), wreszcie ta konkretna implementacja zwraca obserwowalne do pliku ContractComponent.ts, a ten subskrybuje, aby zaktualizować widok.
RestClientService.ts:
ContractService.ts:
ContractComponent.ts:
źródło
Napisałem klasę pamięci podręcznej,
Wszystko jest statyczne ze względu na to, jak go używamy, ale możesz uczynić go normalną klasą i usługą. Nie jestem jednak pewien, czy angular utrzymuje cały czas jedną instancję (nowość w Angular2).
I tak to wykorzystuję:
Zakładam, że może istnieć bardziej sprytny sposób, który wymagałby pewnych
Observable
sztuczek, ale było to w porządku dla moich celów.źródło
Wystarczy użyć tej warstwy pamięci podręcznej, robi wszystko, czego potrzebujesz, a nawet zarządzać pamięcią podręczną dla żądań ajax.
http://www.ravinderpayal.com/blogs/12Jan2017-Ajax-Cache-Mangement-Angular2-Service.html
Jest to bardzo łatwe w użyciu
Warstwa (jako iniekcyjna usługa kątowa) jest
źródło
To jest
.publishReplay(1).refCount();
lub.publishLast().refCount();
odkąd obserwacje Angular Http są kompletne na żądanie.Ta prosta klasa buforuje wynik, dzięki czemu możesz subskrybować .value wiele razy i wysyła tylko 1 żądanie. Możesz także użyć funkcji .reload (), aby złożyć nowe żądanie i opublikować dane.
Możesz go używać w następujący sposób:
i źródło:
źródło
Możesz zbudować prostą klasę Cacheable <>, która pomaga zarządzać danymi pobranymi z serwera http przez wielu subskrybentów:
Stosowanie
Zadeklaruj obiekt buforowalny <> (prawdopodobnie jako część usługi):
i przewodnik:
Zadzwoń z komponentu:
Możesz subskrybować kilka komponentów.
Więcej szczegółów i przykład kodu można znaleźć tutaj: http://devinstance.net/articles/20171021/rxjs-cacheable
źródło
Możesz po prostu użyć pamięci podręcznej ngx ! To lepiej pasuje do twojego scenariusza.
Twoja klasa usług byłaby mniej więcej taka -
Oto link, aby uzyskać więcej informacji.
źródło
Czy próbowałeś uruchomić kod, który już masz?
Ponieważ konstruujesz Obserowalny z obietnicy wynikającej z tego
getJSON()
, żądanie sieciowe jest wysyłane zanim ktokolwiek zasubskrybuje. Otrzymana obietnica jest podzielana przez wszystkich subskrybentów.źródło