Bislang wurden viele Artikel verfasst, die Sie zum Abbestellen von Observable RxJS-Abonnements benötigen. Andernfalls tritt ein Speicherverlust auf. Für die meisten Leser solcher Artikel war die feste Regel „Unterzeichnet? - Abmelden!“ In meinem Kopf verankert. Leider sind die Informationen in solchen Artikeln oft verzerrt oder es wird etwas nicht ausgehandelt, und noch schlimmer, wenn Konzepte ersetzt werden. Wir werden darüber reden.

Nehmen Sie zum Beispiel diesen Artikel: https://medium.com/ngx/why-do-you-need-unsubscribe-ee0c62b5d21f

Wenn sie mir sagen, dass sich die Produktivität möglicherweise verringern könnte, denke ich sofort an eine vorzeitige Optimierung .


Wir lesen den Artikel des Mannes unter dem Spitznamen Reactive Fox weiter :


Weiterhin gibt es nützliche Informationen und Tipps. Ich bin damit einverstanden, dass Sie sich immer von endlosen Streams in RxJS abmelden sollten. Ich konzentriere mich aber nur auf schädliche Informationen (meiner Meinung nach).

Wow ... hat den Schrecken eingeholt. Solche unbegründete Einschüchterung (keine Metriken, Zahlen ...) hat zur Zeit dazu geführt, dass für eine sehr große Anzahl von Front-Endors das Fehlen einer Abmeldung wie ein roter Lappen für einen Bullen ist. Wenn sie darauf stoßen, sehen sie nichts mehr als diesen Lappen.

Der Autor des Artikels machte sogar eine Demo-Anwendung, in der er versuchte, seine Gedanken zu beweisen:
https://stackblitz.com/edit/why-you-have-to-unsubscribe-from-observable-material
In der Tat können Sie auf seinem Stand sehen, wie der Prozessor unnötig arbeitet (wenn ich nichts anklicke) und wie sich der Speicherverbrauch erhöht (kleine Änderung):

Als Bestätigung für die Tatsache, dass Sie sich immer von Abonnements für Observable HttpClient- Anfragen abmelden müssen, fügte er einen Request Interceptor hinzu, der in der Konsole "noch am Leben ... noch am Leben ... noch am Leben ..." anzeigt:

Das heißt Die Person hat den endgültigen Stream abgefangen und ihn unendlich gemacht (im Falle eines Fehlers wird die Anforderung wiederholt, der Fehler tritt jedoch immer auf) und gibt dies als Beweis dafür an, dass Sie den endgültigen Stream abbestellen müssen.
StackBlitz eignet sich nicht besonders zur Messung der Anwendungsleistung Während des Upgrades findet eine automatische Synchronisierung statt, die Ressourcen beansprucht. Also habe ich meinen Testantrag gestellt: https://github.com/andchir/test-angular-app
Dort gibt es zwei Fenster. Bei jedem Öffnen wird eine Anfrage an action.php gesendet, in der eine Verzögerung von 3 Sekunden auftritt , um einen sehr ressourcenintensiven Vorgang nachzuahmen . Action.php protokolliert auch alle Anfragen in der log.txt- Datei.
Action.php Code<?php header('Content-Type: application/json'); function logging($str, $fileName = 'log.txt') { if (is_array($str)) { $str = json_encode($str); } $rootPath = __DIR__; $logFilePath = $rootPath . DIRECTORY_SEPARATOR . $fileName; $options = [ 'max_log_size' => 200 * 1024 ]; if (!is_dir(dirname($logFilePath))) { mkdir(dirname($logFilePath)); } if (file_exists($logFilePath) && filesize($logFilePath) >= $options['max_log_size']) { unlink($logFilePath); } $fp = fopen( $logFilePath, 'a' ); $dateFormat = 'd/m/YH:i:s'; $str = PHP_EOL . PHP_EOL . date($dateFormat) . PHP_EOL . $str; fwrite( $fp, $str ); fclose( $fp ); return true; } $actionName = isset($_GET['a']) && !is_array($_GET['a']) ? $_GET['a'] : '1'; logging("STARTED-{$actionName}"); sleep(3);
Aber zuerst ein kleiner Exkurs. In der Abbildung unten (anklickbar) sehen Sie ein einfaches Beispiel für die Funktionsweise des JavaScript-Garbage Collectors im Chrome-Browser. PUSH ist aufgetreten, aber setTimeout hat den Garbage Collector nicht daran gehindert, den Speicher zu löschen.

Vergessen Sie nicht, den Garbage Collector auf Knopfdruck anzurufen, wenn Sie experimentieren.

Kehren wir zu meiner Testanwendung zurück. Hier ist der Code für beide Fenster:
BadModalComponent Code @Component({ selector: 'app-bad-modal', templateUrl: './bad-modal.component.html', styleUrls: ['./bad-modal.component.css'], providers: [HttpClient] }) export class BadModalComponent implements OnInit, OnDestroy { loading = false; largeData: number[] = (new Array(1000000)).fill(1); destroyed$ = new Subject<void>(); data: DataInterface; constructor( private http: HttpClient, private bsModalRef: BsModalRef ) { } ngOnInit() { this.loadData(); } loadData(): void { // For example only, not for production. this.loading = true; const subscription = this.http.get<DataInterface>('/action.php?a=2').pipe( takeUntil(this.destroyed$), catchError((err) => throwError(err.message)), finalize(() => console.log('FINALIZE')) ) .subscribe({ next: (res) => { setTimeout(() => { console.log(subscription.closed ? 'SUBSCRIPTION IS CLOSED' : 'SUBSCRIPTION IS NOT CLOSED!'); }, 0); console.log('LOADED'); this.data = res; this.loading = false; }, error: (error) => { setTimeout(() => { console.log(subscription.closed ? 'ERROR - SUBSCRIPTION IS CLOSED' : 'ERROR - SUBSCRIPTION IS NOT CLOSED!'); }, 0); console.log('ERROR', error); }, complete: () => { setTimeout(() => { console.log(subscription.closed ? 'COMPLETED - SUBSCRIPTION IS CLOSED' : 'COMPLETED - SUBSCRIPTION IS NOT CLOSED!'); }, 0); console.log('COMPLETED'); } }); } close(event?: MouseEvent): void { if (event) { event.preventDefault(); } this.bsModalRef.hide(); } ngOnDestroy() { console.log('DESTROY'); this.destroyed$.next(); this.destroyed$.complete(); } }
Wie Sie sehen, gibt es eine Abmeldung (takeUntil). Alles, was der "Lehrer" uns geraten hat. Es gibt auch eine große Auswahl.
GoodModalComponent-Code @Component({ selector: 'app-good-modal', templateUrl: './good-modal.component.html', styleUrls: ['./good-modal.component.css'] }) export class GoodModalComponent implements OnInit, OnDestroy { loading = false; largeData: number[] = (new Array(1000000)).fill(1); data: DataInterface; constructor( private http: HttpClient, private bsModalRef: BsModalRef ) { } ngOnInit() { this.loadData(); } loadData(): void { // For example only, not for production. this.loading = true; const subscription = this.http.get<DataInterface>('/action.php?a=1').pipe( catchError((err) => throwError(err.message)), finalize(() => console.log('FINALIZE')) ) .subscribe({ next: (res) => { setTimeout(() => { console.log(subscription.closed ? 'SUBSCRIPTION IS CLOSED' : 'SUBSCRIPTION IS NOT CLOSED!'); }, 0); console.log('LOADED'); this.data = res; this.loading = false; }, error: (error) => { setTimeout(() => { console.log(subscription.closed ? 'ERROR - SUBSCRIPTION IS CLOSED' : 'ERROR - SUBSCRIPTION IS NOT CLOSED!'); }, 0); console.log('ERROR', error); }, complete: () => { setTimeout(() => { console.log(subscription.closed ? 'COMPLETED - SUBSCRIPTION IS CLOSED' : 'COMPLETED - SUBSCRIPTION IS NOT CLOSED!'); }, 0); console.log('COMPLETED'); } }); } close(event?: MouseEvent): void { if (event) { event.preventDefault(); } this.bsModalRef.hide(); } ngOnDestroy() { console.log('DESTROY'); } }
Es gibt genau die gleiche Eigenschaft mit einem großen Array, aber es gibt keine Abmeldung. Und das hindert mich nicht daran, dieses Fenster als gut zu bezeichnen. Warum - später.
Sehen Sie sich das Video an:
Wie Sie in beiden Fällen sehen können, hat der Garbage Collector nach dem Wechsel zur zweiten Komponente den Speicher erfolgreich auf normale Werte zurückgesetzt. Ja, man könnte es möglich machen, den Speicher auch nach dem Schließen der Fenster zu löschen, aber in unserem Experiment ist dies nicht wichtig. Es stellte sich heraus, dass der "Lehrer" sich geirrt hatte, als er sagte:
Sie haben beispielsweise eine Anforderung gestellt. Wenn die Antwort jedoch noch nicht vom Backend eingegangen ist, wird die Komponente als unnötig zerstört. In Ihrem Abonnement wird die Verknüpfung zur Komponente beibehalten, wodurch ein potenzieller Speicherverlust verursacht wird.
Ja, er spricht von einem „potenziellen“ Leck. Wenn der Stream jedoch endlich ist, tritt kein Speicherverlust auf.
Ich sehe die empörten Ausrufe solcher "Lehrer" voraus. Sie werden uns auf jeden Fall etwas sagen wie: "OK, es gibt keinen Speicherverlust, aber durch das Abbestellen brechen wir auch die Anfrage ab , was bedeutet, dass wir sicher sind, dass kein Code mehr ausgeführt wird, nachdem wir eine Antwort vom Server erhalten haben . " Erstens sage ich nicht, dass das Abbestellen immer schlecht ist. Ich sage nur, dass Sie Konzepte ersetzen . Ja, die Tatsache, dass nach Eingang der Antwort eine andere unbrauchbare Operation ausgeführt wird, ist schlecht, aber Sie können sich nur durch Abbestellen (in diesem Fall) vor einem echten Speicherverlust und auf andere Weise vor anderen unerwünschten Effekten schützen. Sie müssen die Leser nicht einschüchtern und ihnen ihren eigenen Stil beim Schreiben von Code aufzwingen.
Müssen wir die Anfrage immer stornieren, wenn der Benutzer seine Meinung ändert? Nicht immer! Vergessen Sie nicht, dass Sie die Anforderung abbrechen, den Vorgang auf dem Server jedoch nicht abbrechen . Stellen Sie sich vor, ein Benutzer hat eine Komponente geöffnet, etwas wurde schon lange geladen und er wechselt zu einer anderen Komponente. Möglicherweise ist der Server geladen und verarbeitet nicht alle Anforderungen und Vorgänge. In diesem Fall kann der Benutzer alle Links in der Navigation hektisch anstoßen und den Server noch stärker belasten , da die Anforderung auf der Serverseite nicht gestoppt wird (in den meisten Fällen).
Sehen Sie sich das folgende Video an:
Ich habe den Benutzer auf eine Antwort warten lassen. In den meisten Fällen erfolgt die Antwort schnell und der Benutzer hat keine Unannehmlichkeiten. Auf diese Weise wird der Server jedoch vor wiederholten schweren Vorgängen bewahrt.
Zusammenfassung:
- Ich sage nicht, dass Sie sich nicht von RxJS-Abonnements für HttpClient-Anfragen abmelden müssen. Ich sage nur, dass es Zeiten gibt, in denen dies nicht notwendig ist. Konzepte müssen nicht ersetzt werden. Wenn Sie von einem Speicherverlust sprechen, zeigen Sie diesen Fehler an. Nicht dein endloses console.log , nämlich ein Leck. Worin wird das Gedächtnis gemessen? In welcher Zeit wird die Operation gemessen? Das muss gezeigt werden.
- Ich bezeichne meine Lösung, die ich in der Testanwendung angewendet habe, nicht als "Wunderwaffe". Im Gegenteil, ich fordere den Fronttender nachdrücklich auf, mehr Freiheit zu erhalten. Lassen Sie ihn entscheiden, wie er seinen Code schreibt. Keine Notwendigkeit, ihn einzuschüchtern und seinen eigenen Entwicklungsstil durchzusetzen.
- Ich bin gegen Fanatismus und vorzeitige Optimierung. Ich habe in letzter Zeit zu viel davon gesehen.
- Der Browser verfügt über erweiterte Methoden zum Auffinden von Speicherlecks als die von mir gezeigte. Ich denke in meinem Fall ist die Anwendung dieser einfachen Methode ausreichend. Ich empfehle Ihnen jedoch, sich mit dem Thema näher vertraut zu machen, z. B. in diesem Artikel: https://habr.com/de/post/309318/ .
UPD # 1
Im Moment sackte die Post fast einen Tag lang ab. Zuerst ging er zu den Vor- und Nachteilen über, dann stoppte die Bewertung bei Null. Dies bedeutet, dass das Publikum genau in zwei Lager aufgeteilt wurde. Ich weiß nicht, ob das gut oder schlecht ist.
UPD # 2
In den Kommentaren erschien Jet Fox (der Autor des zu analysierenden Artikels). Anfangs bedankte er sich bei mir, er war sehr höflich. Aber als er die Passivität des Publikums sah, begann er Druck auszuüben. Es kam zu dem Punkt, dass er schrieb, dass ich mich entschuldigen sollte. Das heißt er hat gelogen (liegt mit einem gelben Rahmen oben umrandet), und ich sollte mich entschuldigen.
Zuerst dachte ich, dass das Abfangen von Streams mit endlosen Wiederholungen (also 2-3 Wiederholungen), das er in seiner Demo-Anwendung geschrieben hat, nur zu Testzwecken und zur Information dient. Aber es stellte sich heraus, dass er ihn als Beispiel aus dem Leben ansah. Das heißt den Knopf eines Fensters zu blockieren - es ist unmöglich . Und um solche Abfangjäger zu erstellen, die gegen die Prinzipien von SOLID verstoßen, die Modularität der Anwendung verletzen (Module und Komponenten müssen voneinander unabhängig sein), können Sie die Komponententests Ihrer Einheiten (Komponenten, Dienste) durch die Gesamtstruktur führen. Stellen Sie sich die Situation vor: Sie haben eine Komponente geschrieben, Unit-Tests dafür geschrieben. Und dann erscheint ein solcher Fox, fügt Ihrer Anwendung einen ähnlichen Interceptor hinzu, und Ihre Tests werden unbrauchbar. Dann sagt er immer noch zu dir: "Warum hast du nicht vorhergesagt, dass ich einen solchen Abfangjäger hinzufügen möchte? Nun, korrigiere deinen Code." Vielleicht ist dies eine Realität in seinem Team, aber ich denke nicht, dass dies gefördert oder ein Auge zugedrückt werden sollte.
UPD # 3
In den Kommentaren geht es hauptsächlich um Abonnements und Abbestellungen. Ist ein Beitrag namens "Evil abbestellen"? Nein. Ich fordere Sie nicht auf, sich nicht abzumelden. Machen Sie es wie zuvor. Aber Sie müssen verstehen, warum Sie das tun. Das Abbestellen ist keine vorzeitige Optimierung. Wenn Sie jedoch den Weg des Schutzes vor potenziellen Bedrohungen beschreiten (wie uns der Autor des zu analysierenden Artikels nennt), können Sie die Grenze überschreiten. Dann kann Ihr Code überlastet und schwer zu warten sein.
In diesem Artikel geht es um Fanatismus, der zur Verbreitung nicht überprüfter Informationen führt. In einigen Fällen ist es notwendig, sich ruhiger auf das Fehlen einer Abmeldung zu beziehen (Sie müssen klar verstehen, ob in einem bestimmten Fall ein Problem vorliegt).
UPD # 4
Im Gegenteil, ich fordere den Fronttender nachdrücklich auf, mehr Freiheit zu erhalten. Lassen Sie ihn entscheiden, wie er seinen Code schreibt.
Hier muss geklärt werden. Ich bin für Standards. Der Standard kann jedoch vom Autor der Bibliothek oder seinem Team festgelegt werden, während dies nicht der Fall ist (in der Dokumentation und offiziell). Die Symfony-Framework-Dokumentation enthält beispielsweise einen Abschnitt mit bewährten Methoden . Wenn es in der RxJS-Dokumentation dasselbe wäre und "signiert-abbestellen" sagen würde, hätte ich nicht den Wunsch, mit ihm zu streiten.
UPD # 5
Wichtiger Kommentar mit Antworten von seriösen Personen:
https://habr.com/de/post/479732/#comment_21012620
Die Empfehlung, den vom RxJS-Entwickler unterzeichneten Vertrag zum Abbestellen zu erfüllen, besteht , jedoch inoffiziell.