Mit Http rufen wir eine Methode auf, die einen Netzwerkaufruf ausführt und eine beobachtbare http zurückgibt:
getCustomer() {
return this.http.get('/someUrl').map(res => res.json());
}
Wenn wir dies beobachten und mehrere Abonnenten hinzufügen:
let network$ = getCustomer();
let subscriber1 = network$.subscribe(...);
let subscriber2 = network$.subscribe(...);
Wir möchten sicherstellen, dass dies nicht zu mehreren Netzwerkanforderungen führt.
Dies mag wie ein ungewöhnliches Szenario erscheinen, ist aber tatsächlich recht häufig: Wenn der Anrufer beispielsweise das Observable abonniert, um eine Fehlermeldung anzuzeigen, und es mithilfe der asynchronen Pipe an die Vorlage weiterleitet, haben wir bereits zwei Abonnenten.
Wie geht das in RxJs 5 richtig?
Das scheint nämlich gut zu funktionieren:
getCustomer() {
return this.http.get('/someUrl').map(res => res.json()).share();
}
Aber ist dies die idiomatische Art, dies in RxJs 5 zu tun, oder sollten wir stattdessen etwas anderes tun?
Hinweis: Gemäß Angular 5 new HttpClient
ist der .map(res => res.json())
Teil in allen Beispielen jetzt unbrauchbar, da das JSON-Ergebnis jetzt standardmäßig angenommen wird.
quelle
Antworten:
Cache die Daten und wenn verfügbar zwischengespeichert, gib dies zurück, andernfalls mache die HTTP-Anfrage.
Plunker Beispiel
Dieser Artikel https://blog.ehowtram.io/angular/2018/03/05/advanced-caching-with-rxjs.html ist eine großartige Erklärung, wie man mit Cache arbeitet
shareReplay
.quelle
do()
im Gegensatz zumap()
ändert das Ereignis nicht. Sie könnten auch verwendenmap()
, mussten dann aber sicherstellen, dass am Ende des Rückrufs der richtige Wert zurückgegeben wird..subscribe()
tut, den Wert nicht benötigt, können Sie das tun, weil es möglicherweise nur wirdnull
(abhängig davon, wasthis.extractData
zurückgibt), aber meiner Meinung nach drückt dies die Absicht des Codes nicht gut aus.this.extraData
wieextraData() { if(foo) { doSomething();}}
sonst endet, wird das Ergebnis des letzten Ausdrucks zurückgegeben, der möglicherweise nicht Ihren Wünschen entspricht.if (this.observable) { return this.observable; } else { this.observable = this.http.get(url) .map(res => res.json().data); return this.observable; }
Laut @ Christian-Vorschlag ist dies eine Möglichkeit, die für HTTP-Observables gut funktioniert, die nur einmal emittieren und dann abgeschlossen werden:
quelle
share
Bediener möglicherweise eine vernünftige Wahl (wenn auch mit einigen bösen Randfällen). Für eine ausführlichepublishLast().refCount()
nicht gekündigt werden kannrefCount
, wird der Nettoeffekt , dass alle beobachtbaren Quellen abgemeldet werden, wenn alle Abonnements der beobachtbaren Quelle, die von zurückgegeben werden, storniert werden, wenn sie "Inflight"UPDATE: Ben Lesh sagt, dass Sie in der nächsten Nebenversion nach 5.2.0 einfach shareReplay () aufrufen können, um wirklich zwischenzuspeichern.
VORHER.....
Verwenden Sie erstens nicht share () oder PublishReplay (1) .refCount (), sie sind identisch und das Problem dabei ist, dass sie nur freigegeben werden, wenn Verbindungen hergestellt werden, während das Observable aktiv ist, wenn Sie nach Abschluss eine Verbindung herstellen , es schafft wieder eine neue beobachtbare Übersetzung, nicht wirklich Caching.
Birowski hat oben die richtige Lösung angegeben, nämlich ReplaySubject zu verwenden. ReplaySubject speichert die von Ihnen angegebenen Werte (bufferSize) in unserem Fall 1 zwischen. Sobald refCount Null erreicht und Sie eine neue Verbindung herstellen, wird kein neues beobachtbares like share () erstellt. Dies ist das richtige Verhalten für das Caching.
Hier ist eine wiederverwendbare Funktion
Hier erfahren Sie, wie Sie es verwenden
Im Folgenden finden Sie eine erweiterte Version der zwischenspeicherbaren Funktion. Diese ermöglicht eine eigene Nachschlagetabelle + die Möglichkeit, eine benutzerdefinierte Nachschlagetabelle bereitzustellen. Auf diese Weise müssen Sie this._cache nicht wie im obigen Beispiel überprüfen. Beachten Sie auch, dass Sie anstelle der Übergabe der Observable als erstes Argument eine Funktion übergeben, die die Observables zurückgibt. Dies liegt daran, dass Angulars HTTP sofort ausgeführt wird. Wenn Sie also eine verzögert ausgeführte Funktion zurückgeben, können Sie entscheiden, sie nicht aufzurufen, wenn sie bereits vorhanden ist unser Cache.
Verwendungszweck:
quelle
const data$ = this._http.get('url').pipe(cacheable()); /*1st subscribe*/ data$.subscribe(); /*2nd subscribe*/ data$.subscribe();
? So verhält es sich mehr wie jeder andere Operator ..rxjs 5.4.0 verfügt über eine neue shareReplay- Methode.
Der Autor sagt ausdrücklich "ideal für den Umgang mit Dingen wie dem Zwischenspeichern von AJAX-Ergebnissen"
rxjs PR # 2443 feat (shareReplay): fügt eine
shareReplay
Variante von hinzupublishReplay
quelle
nach diesem Artikel
also drinnen, wenn Anweisungen nur angehängt werden
zu
.map(...)
quelle
rxjs Version 5.4.0 (2017-05-09) bietet Unterstützung für shareReplay .
Sie können einen Winkeldienst leicht ändern, um dies zu verwenden, und ein Observable mit einem zwischengespeicherten Ergebnis zurückgeben, das den http-Aufruf immer nur einmal ausführt (vorausgesetzt, der erste Aufruf war erfolgreich).
Beispiel Angular Service
Hier ist ein sehr einfacher Kundenservice, der verwendet
shareReplay
.customer.service.ts
Beachten Sie, dass die Zuweisung im Konstruktor in die Methode verschoben werden kann. Da
getCustomers
jedoch die von zurückgegebenen ObservablenHttpClient
"kalt" sind , ist dies im Konstruktor akzeptabel, da der http-Aufruf nur bei jedem ersten Aufruf von ausgeführt wirdsubscribe
.Hierbei wird auch davon ausgegangen, dass die ursprünglich zurückgegebenen Daten während der Lebensdauer der Anwendungsinstanz nicht veraltet sind.
quelle
Ich habe die Frage mit einem Stern versehen, aber ich werde versuchen, es zu versuchen.
Hier ist der Beweis :)
Es gibt nur einen Imbiss:
getCustomer().subscribe(customer$)
Wir abonnieren nicht die API-Antwort von
getCustomer()
, wir abonnieren ein beobachtbares ReplaySubject, das auch ein anderes Observable abonnieren kann, und (und das ist wichtig) halten Sie den zuletzt ausgegebenen Wert und veröffentlichen Sie ihn erneut auf einem seiner (ReplaySubjects) ) Abonnenten.quelle
Ich habe eine Möglichkeit gefunden, das http get-Ergebnis in sessionStorage zu speichern und für die Sitzung zu verwenden, damit der Server nie wieder aufgerufen wird.
Ich habe es verwendet, um die Github-API aufzurufen, um Nutzungsbeschränkungen zu vermeiden.
Zu Ihrer Information, das SessionStorage-Limit beträgt 5 Millionen (oder 4,75 Millionen). Daher sollte es für große Datenmengen nicht so verwendet werden.
------ edit -------------
Wenn Sie Daten mit F5 aktualisieren möchten, das Speicherdaten anstelle von sessionStorage verwendet;
quelle
sessionStorage
sagt, würde ich es nur für Daten verwenden, von denen erwartet wird , dass sie für die gesamte Sitzung konsistent sind.getCustomer
in seinem Beispiel hat. ;) Also wollte nur ein paar Leute warnen, die die Risiken vielleicht nicht sehen :)Die von Ihnen gewählte Implementierung hängt davon ab, ob unsubscribe () Ihre HTTP-Anfrage abbrechen soll oder nicht.
In jedem Fall sind TypeScript-Dekoratoren eine gute Möglichkeit, das Verhalten zu standardisieren. Dies ist der, den ich geschrieben habe:
Dekorateur Definition:
quelle
Property 'connect' does not exist on type '{}'.
von der LiniereturnValue.connect();
. Können Sie das näher erläutern?Zwischenspeicherbare HTTP-Antwortdaten mit Rxjs Observer / Observable + Caching + Subscription
Siehe Code unten
* Haftungsausschluss: Ich bin neu bei rxjs. Denken Sie also daran, dass ich den Observable / Observer-Ansatz möglicherweise missbrauche. Meine Lösung ist lediglich ein Konglomerat anderer Lösungen, die ich gefunden habe, und ist die Folge davon, dass ich keine einfache, gut dokumentierte Lösung gefunden habe. Daher biete ich meine vollständige Codelösung (wie ich sie gerne gefunden hätte) in der Hoffnung an, dass sie anderen hilft.
* Beachten Sie, dass dieser Ansatz lose auf GoogleFirebaseObservables basiert. Leider fehlt mir die richtige Erfahrung / Zeit, um zu wiederholen, was sie unter der Haube getan haben. Das Folgende ist jedoch eine vereinfachte Methode, um einen asynchronen Zugriff auf einige cachefähige Daten bereitzustellen.
Situation : Eine 'Produktlisten'-Komponente hat die Aufgabe, eine Produktliste anzuzeigen. Die Website ist eine einseitige Web-App mit einigen Menüschaltflächen, mit denen die auf der Seite angezeigten Produkte "gefiltert" werden.
Lösung : Die Komponente "abonniert" eine Servicemethode. Die Servicemethode gibt ein Array von Produktobjekten zurück, auf die die Komponente über den Abonnement-Rückruf zugreift. Die Servicemethode packt ihre Aktivität in einen neu erstellten Observer und gibt den Observer zurück. Innerhalb dieses Beobachters sucht er nach zwischengespeicherten Daten und gibt sie an den Abonnenten (die Komponente) zurück und gibt sie zurück. Andernfalls wird ein http-Aufruf zum Abrufen der Daten ausgegeben, die Antwort abonniert, in der Sie diese Daten verarbeiten (z. B. die Daten Ihrem eigenen Modell zuordnen) und die Daten dann an den Abonnenten zurückgeben können.
Der Code
product-list.component.ts
product.service.ts
product.ts (das Modell)
Hier ist ein Beispiel für die Ausgabe, die ich beim Laden der Seite in Chrome sehe. Beachten Sie, dass beim ersten Laden die Produkte von http abgerufen werden (Aufruf meines Node Rest-Dienstes, der lokal auf Port 3000 ausgeführt wird). Wenn ich dann auf klicke, um zu einer gefilterten Ansicht der Produkte zu navigieren, werden die Produkte im Cache gefunden.
Mein Chrome-Protokoll (Konsole):
... [klickte auf eine Menüschaltfläche, um die Produkte zu filtern] ...
Fazit: Dies ist der einfachste Weg, den ich (bisher) gefunden habe, um zwischengespeicherte http-Antwortdaten zu implementieren. In meiner eckigen App wird die Produktlistenkomponente jedes Mal neu geladen, wenn ich zu einer anderen Ansicht der Produkte navigiere. ProductService scheint eine gemeinsam genutzte Instanz zu sein, daher bleibt der lokale Cache von 'products: Product []' im ProductService während der Navigation erhalten, und nachfolgende Aufrufe von "GetProducts ()" geben den zwischengespeicherten Wert zurück. Ein letzter Hinweis: Ich habe Kommentare darüber gelesen, wie Observables / Abonnements geschlossen werden müssen, wenn Sie fertig sind, um Speicherverluste zu vermeiden. Ich habe dies hier nicht aufgenommen, aber es ist etwas zu beachten.
quelle
Ich gehe davon aus, dass @ ngx-cache / core nützlich sein könnte, um die Caching-Funktionen für die http-Aufrufe beizubehalten, insbesondere wenn der HTTP-Aufruf sowohl auf Browser- als auch auf Serverplattformen erfolgt .
Angenommen, wir haben die folgende Methode:
Sie können die Verwendung
Cached
Dekorateur von @ NGX-Cache / Kern den zurückgegebenen Wert aus dem Verfahren zum Speichern der HTTP - Aufruf an die Herstellungcache storage
( diestorage
konfigurierbar sein kann, überprüfen Sie bitte die Umsetzung bei ng-Samen / universal ) - direkt auf der ersten Ausführung. Beim nächsten Aufruf der Methode (unabhängig von Browser oder Serverplattform ) wird der Wert von der abgerufencache storage
.Es gibt auch die Möglichkeit zur Verwendung Caching - Methoden (
has
,get
,set
) mit dem Caching API .anyclass.ts
Hier ist die Liste der Pakete, sowohl für das clientseitige als auch für das serverseitige Caching:
quelle
rxjs 5.3.0
Ich war nicht glücklich mit
.map(myFunction).publishReplay(1).refCount()
Mit mehreren Teilnehmern,
.map()
führtmyFunction
zweimal in einigen Fällen (Ich erwarte , dass es nur einmal ausführen). Ein Fix scheint zu seinpublishReplay(1).refCount().take(1)
Eine andere Sache, die Sie tun können, ist
refCount()
, das Observable nicht sofort zu verwenden und heiß zu machen:Dadurch wird die HTTP-Anforderung unabhängig von den Abonnenten gestartet. Ich bin nicht sicher, ob das Abbestellen vor Abschluss des HTTP-GET das Programm abbricht oder nicht.
quelle
Mein persönlicher Favorit ist die Verwendung von
async
Methoden für Anrufe, die Netzwerkanforderungen stellen. Die Methoden selbst geben keinen Wert zurück, sondern aktualisieren einenBehaviorSubject
innerhalb desselben Dienstes, den die Komponenten abonnieren.Warum nun ein
BehaviorSubject
statt eines verwendenObservable
? Weil,onnext
.getValue()
Methode verwenden.Beispiel:
customer.service.ts
Dann können wir, wo immer erforderlich, einfach abonnieren
customers$
.Oder vielleicht möchten Sie es direkt in einer Vorlage verwenden
Bis Sie einen weiteren Aufruf tätigen
getCustomers
, bleiben die Daten imcustomers$
BehaviorSubject erhalten.Was ist, wenn Sie diese Daten aktualisieren möchten? Rufen Sie einfach an
getCustomers()
Mit dieser Methode müssen wir die Daten zwischen nachfolgenden Netzwerkaufrufen nicht explizit beibehalten, da sie von der verarbeitet werden
BehaviorSubject
.PS: Wenn eine Komponente zerstört wird, empfiehlt es sich normalerweise, die Abonnements zu entfernen. Dazu können Sie die in dieser Antwort vorgeschlagene Methode verwenden .
quelle
Tolle Antworten.
Oder Sie könnten dies tun:
Dies ist aus der neuesten Version von rxjs. Ich verwende die Version 5.5.7 von RxJS
quelle
Rufen Sie einfach share () nach der Karte und vor dem Abonnieren auf .
In meinem Fall habe ich einen generischen Dienst (RestClientService.ts), der den Restaufruf ausführt, Daten extrahiert, auf Fehler prüft und beobachtbar an einen konkreten Implementierungsdienst (z. B. ContractClientService.ts) zurückgibt, schließlich diese konkrete Implementierung Gibt beobachtbar an de ContractComponent.ts zurück, und dieser abonniert, um die Ansicht zu aktualisieren.
RestClientService.ts:
ContractService.ts:
ContractComponent.ts:
quelle
Ich habe eine Cache-Klasse geschrieben,
Es ist alles statisch, weil wir es verwenden, aber Sie können es zu einer normalen Klasse und einem Service machen. Ich bin mir nicht sicher, ob Angular eine einzelne Instanz für die gesamte Zeit behält (neu in Angular2).
Und so benutze ich es:
Ich nehme an, es könnte einen klügeren Weg geben, der einige
Observable
Tricks verwendet, aber das war für meine Zwecke in Ordnung.quelle
Verwenden Sie einfach diese Cache-Ebene, sie erledigt alles, was Sie benötigen, und verwaltet sogar den Cache für Ajax-Anforderungen.
http://www.ravinderpayal.com/blogs/12Jan2017-Ajax-Cache-Mangement-Angular2-Service.html
Es ist so einfach zu bedienen
Die Schicht (als injizierbarer Winkeldienst) ist
quelle
Es ist
.publishReplay(1).refCount();
oder.publishLast().refCount();
seit Angular Http Observables nach Anfrage abgeschlossen.Diese einfache Klasse speichert das Ergebnis zwischen, sodass Sie .value viele Male abonnieren können, und stellt nur eine Anfrage. Sie können auch .reload () verwenden, um neue Anforderungen zu stellen und Daten zu veröffentlichen.
Sie können es verwenden wie:
und die Quelle:
quelle
Sie können eine einfache Klasse Cacheable <> erstellen, mit der Sie Daten verwalten können, die vom HTTP-Server mit mehreren Abonnenten abgerufen wurden:
Verwendung
Deklarieren Sie das zwischenspeicherbare <> Objekt (vermutlich als Teil des Dienstes):
und Handler:
Aufruf von einer Komponente:
Sie können mehrere Komponenten abonnieren lassen.
Weitere Details und ein Codebeispiel finden Sie hier: http://devinstance.net/articles/20171021/rxjs-cacheable
quelle
Sie könnten einfach ngx-cacheable verwenden ! Es passt besser zu Ihrem Szenario.
Ihre Serviceklasse wäre also ungefähr so -
Hier ist der Link für weitere Informationen.
quelle
Haben Sie versucht, den bereits vorhandenen Code auszuführen?
Da Sie das Observable aus dem daraus resultierenden Versprechen erstellen
getJSON()
, wird die Netzwerkanforderung gestellt, bevor sich jemand anmeldet. Das daraus resultierende Versprechen wird von allen Abonnenten geteilt.quelle