Apakah perlu untuk berhenti berlangganan dari observasi yang dibuat oleh metode Http?

211

Apakah Anda perlu berhenti berlangganan dari panggilan http Angular 2 untuk mencegah kebocoran memori?

 fetchFilm(index) {
        var sub = this._http.get(`http://example.com`)
            .map(result => result.json())
            .map(json => {
                dispatch(this.receiveFilm(json));
            })
            .subscribe(e=>sub.unsubscribe());
            ...
born2net
sumber
1
Kemungkinan duplikat Mencegah kebocoran memori di Angular 2?
Eric Martinez
1
Lihat stackoverflow.com/questions/34461842/… (dan komentarnya juga)
Eric Martinez

Jawaban:

253

Jadi jawabannya tidak, Anda tidak. Ng2akan membersihkannya sendiri.

Sumber layanan Http, dari sumber backend Http XHR Angular:

masukkan deskripsi gambar di sini

Perhatikan bagaimana menjalankannya complete()setelah mendapatkan hasilnya. Ini berarti sebenarnya berhenti berlangganan saat selesai. Jadi, Anda tidak perlu melakukannya sendiri.

Berikut ini adalah tes untuk memvalidasi:

  fetchFilms() {
    return (dispatch) => {
        dispatch(this.requestFilms());

        let observer = this._http.get(`${BASE_URL}`)
            .map(result => result.json())
            .map(json => {
                dispatch(this.receiveFilms(json.results));
                dispatch(this.receiveNumberOfFilms(json.count));
                console.log("2 isUnsubscribed",observer.isUnsubscribed);
                window.setTimeout(() => {
                  console.log("3 isUnsubscribed",observer.isUnsubscribed);
                },10);
            })
            .subscribe();
        console.log("1 isUnsubscribed",observer.isUnsubscribed);
    };
}

Seperti yang diharapkan, Anda dapat melihat bahwa itu selalu berhenti berlangganan secara otomatis setelah mendapatkan hasil dan selesai dengan operator yang dapat diamati. Ini terjadi pada batas waktu (# 3) sehingga kita dapat memeriksa status yang dapat diamati ketika semuanya selesai dan selesai.

Dan hasilnya

masukkan deskripsi gambar di sini

Jadi, tidak akan ada kebocoran karena Ng2berhenti berlangganan otomatis!

Senang menyebutkan: Ini Observabledikategorikan sebagai finite, sebaliknya infinite Observableyang merupakan aliran data tak terbatas dapat dipancarkan seperti clickpendengar DOM misalnya.

Terima kasih, @rubyboy atas bantuannya.

born2net
sumber
17
apa yang terjadi dalam kasus di mana pengguna meninggalkan halaman sebelum respons masuk, atau jika respons tidak pernah datang sebelum pengguna meninggalkan tampilan? Apakah itu tidak menyebabkan kebocoran?
Thibs
1
Saya ingin tahu hal di atas juga.
1984
4
@ 1984 Jawabannya SELALU TIDAK BERHASIL. Jawaban ini sepenuhnya salah untuk alasan yang dikemukakan dalam komentar ini. Pengguna yang menavigasi adalah tautan memori. Plus, jika kode dalam langganan itu berjalan setelah pengguna bernavigasi, itu dapat menyebabkan kesalahan / memiliki efek samping yang tidak terduga. Saya cenderung menggunakan takeWhile (() => this.componentActive) pada semua yang dapat diobservasi dan cukup atur this.componentActive = false di ngOnDestroy untuk membersihkan semua yang bisa diamati dalam suatu komponen.
Lenny
4
Contoh yang ditampilkan di sini mencakup api pribadi bersudut dalam. dengan api pribadi lainnya, itu dapat berubah dengan peningkatan tanpa pemberitahuan. Aturan praktisnya adalah: Jika tidak didokumentasikan secara resmi, jangan membuat asumsi apa pun. Sebagai contoh, AsynPipe memiliki dokumentasi yang jelas yang secara otomatis berlangganan / berhenti berlangganan untuk Anda. Dokumen HttpClient tidak menyebutkan apa pun tentang itu.
YoussefTaghlabi
1
@YoussefTaghlabi, FYI, dokumentasi sudut resmi ( angular.io/guide/http ) menyebutkan bahwa: "AsyncPipe berlangganan (dan berhenti berlangganan) untuk Anda secara otomatis." Jadi, memang didokumentasikan secara resmi.
Hudgi
96

Apa yang kalian bicarakan !!!

OK jadi ada dua alasan untuk berhenti berlangganan dari yang bisa diamati. Sepertinya tidak ada yang berbicara banyak tentang alasan kedua yang sangat penting!

1) Bersihkan sumber daya. Seperti yang orang lain katakan ini adalah masalah yang dapat diabaikan untuk HTTP yang dapat diamati. Itu hanya akan membersihkan dirinya sendiri.

2) Mencegah subscribepawang lari.

(Untuk HTTP ini sebenarnya juga akan membatalkan permintaan di browser - jadi tidak akan membuang waktu untuk membaca respons. Tapi itu sebenarnya merupakan tambahan untuk poin utama saya di bawah.)

Relevansi nomor 2 akan tergantung pada apa yang dilakukan oleh langganan Anda:

Jika Anda subscribe() fungsi handler memiliki efek samping apa pun yang tidak diinginkan jika apa pun panggilan itu ditutup atau dibuang, Anda harus berhenti berlangganan (atau menambahkan logika kondisional) untuk mencegahnya dieksekusi.

Pertimbangkan beberapa kasus:

1) Formulir login. Anda memasukkan nama pengguna dan kata sandi dan klik 'Login'. Bagaimana jika server lambat dan Anda memutuskan untuk menekan Escape untuk menutup dialog? Anda mungkin akan menganggap Anda tidak masuk, tetapi jika permintaan http kembali setelah Anda menekan escape maka Anda masih akan menjalankan logika apa pun yang Anda miliki di sana. Ini dapat mengakibatkan pengalihan ke halaman akun, cookie login yang tidak diinginkan atau variabel token yang ditetapkan. Ini mungkin bukan yang diharapkan pengguna Anda.

2) Formulir 'kirim email'.

Jika subscribe penangan untuk 'sendEmail' melakukan sesuatu seperti memicu animasi 'Email Anda terkirim', transfer Anda ke halaman lain atau mencoba mengakses apa pun yang telah dibuang Anda mungkin mendapatkan pengecualian atau perilaku yang tidak diinginkan.

Hati-hati juga untuk tidak menganggap unsubscribe()berarti 'membatalkan'. Setelah pesan HTTP dalam penerbanganunsubscribe() TIDAK akan membatalkan permintaan HTTP jika sudah mencapai server Anda. Itu hanya akan membatalkan respons yang kembali kepada Anda. Dan email itu mungkin akan dikirim.

Jika Anda membuat langganan untuk mengirim email langsung ke dalam komponen UI maka Anda mungkin ingin berhenti berlangganan pada saat membuang, tetapi jika email tersebut dikirim oleh layanan terpusat non-UI maka Anda mungkin tidak perlu melakukannya.

3) Komponen sudut yang dihancurkan / ditutup. Setiap http yang dapat diamati masih berjalan pada saat itu akan menyelesaikan dan menjalankan logikanya kecuali Anda berhenti berlanggananonDestroy() . Apakah konsekuensinya sepele atau tidak akan tergantung pada apa yang Anda lakukan dalam pengendali berlangganan. Jika Anda mencoba memperbarui sesuatu yang tidak ada lagi Anda mungkin mendapatkan kesalahan.

Kadang-kadang Anda mungkin memiliki beberapa tindakan yang Anda inginkan jika komponen dibuang, dan beberapa Anda tidak. Misalnya mungkin Anda memiliki suara 'swoosh' untuk email yang dikirim. Anda mungkin ingin ini dimainkan walaupun komponen sudah ditutup, tetapi jika Anda mencoba menjalankan animasi pada komponen itu akan gagal. Jika demikian, beberapa logika kondisional tambahan di dalam berlangganan akan menjadi solusinya - dan Anda TIDAK ingin berhenti berlangganan http yang dapat diamati.

Jadi dalam menjawab pertanyaan aktual, tidak, Anda tidak perlu melakukannya untuk menghindari kebocoran memori. Tetapi Anda perlu melakukannya (sering) untuk menghindari efek samping yang tidak diinginkan dipicu oleh menjalankan kode yang dapat membuang pengecualian atau merusak kondisi aplikasi Anda.

Tip: SubscriptionBerisi closedproperti boolean yang mungkin berguna dalam kasus lanjut. Untuk HTTP ini akan ditetapkan ketika selesai. Dalam Angular mungkin berguna dalam beberapa situasi untuk mengatur _isDestroyedproperti ngDestroyyang dapat diperiksa oleh subscribepawang Anda .

Tips 2: Jika menangani beberapa langganan Anda dapat membuat new Subscription()objek ad-hoc dan add(...)langganan lainnya - jadi ketika Anda berhenti berlangganan dari yang utama, ia akan berhenti berlangganan semua langganan yang ditambahkan juga.

Simon_Weaver
sumber
2
Juga perhatikan bahwa jika Anda memiliki layanan yang mengembalikan overvable http mentah dan Anda kemudian mengirimnya sebelum berlangganan maka Anda hanya perlu berhenti berlangganan dari pengamatan terakhir, bukan http mendasar yang dapat diamati. Bahkan Anda tidak akan memiliki Berlangganan ke http secara langsung sehingga Anda tidak bisa.
Simon_Weaver
Meskipun berhenti berlangganan membatalkan permintaan browser, server tetap bertindak atas permintaan tersebut. Apakah ada cara untuk intimasi server untuk membatalkan operasi juga? Saya mengirimkan permintaan http secara berurutan, yang sebagian besar dibatalkan dengan berhenti berlangganan. Namun, server masih beroperasi pada permintaan yang dibatalkan oleh klien, menyebabkan permintaan yang sah untuk menunggu.
bala
@ Balala Anda harus datang dengan mekanisme Anda sendiri untuk ini - yang tidak akan ada hubungannya dengan RxJS. Misalnya, Anda bisa meletakkan permintaan ke dalam tabel dan menjalankannya setelah 5 detik di latar belakang, maka jika sesuatu perlu dibatalkan Anda hanya akan menghapus atau mengatur bendera pada baris yang lebih tua yang akan menghentikan mereka mengeksekusi. Sepenuhnya akan tergantung pada apa aplikasi Anda. Namun karena Anda menyebutkan server memblokir, itu mungkin dikonfigurasikan untuk hanya mengizinkan satu permintaan pada satu waktu - tetapi itu lagi akan tergantung pada apa yang Anda gunakan.
Simon_Weaver
🔥🔥🔥Tips 2 - adalah PRO-TIP, 🔥🔥🔥 untuk siapa saja yang ingin berhenti berlangganan dari beberapa langganan dengan memanggil hanya satu fungsi. Tambahkan dengan menggunakan metode .add () dan dari .unsubscribe () di ngDestroy.
abhay tripathi
23

Memanggil unsubscribemetode ini bukan untuk membatalkan permintaan HTTP yang sedang berlangsung karena metode ini memanggil yang abortada di objek XHR yang mendasarinya dan menghapus pendengar pada acara memuat dan kesalahan:

// From the XHRConnection class
return () => {
  _xhr.removeEventListener('load', onLoad);
  _xhr.removeEventListener('error', onError);
  _xhr.abort();
};

Yang mengatakan, unsubscribe menghapus pendengar ... Jadi itu bisa menjadi ide yang bagus tapi saya tidak berpikir itu perlu untuk satu permintaan ;-)

Semoga ini bisa membantu Anda, Thierry

Thierry Templier
sumber
: | itu tidak masuk akal: | saya sedang mencari cara untuk berhenti ... tapi saya heran, dan jika itu saya coding, dalam beberapa skenario: saya akan melakukannya seperti ini: y = x.subscribe((x)=>data = x); kemudian pengguna mengubah input dan x.subscribe((x)=>cachlist[n] = x); y.unsubscribe()hei Anda menggunakan sumber daya server kami, saya menang dapat membuangmu ..... dan dalam skenario lain: panggil saja y.stop()semuanya
deadManN
16

Berhenti berlangganan adalah suatu KEHARUSAN jika Anda ingin perilaku deterministik pada semua kecepatan jaringan.

Bayangkan komponen A dirender dalam sebuah tab - Anda mengklik tombol untuk mengirim permintaan 'GET'. Dibutuhkan 200 ms untuk tanggapan untuk kembali. Jadi, Anda aman untuk menutup tab kapan saja mengetahui bahwa, mesin akan lebih cepat daripada Anda & respons http diproses dan selesai sebelum tab ditutup dan komponen A dihancurkan.

Bagaimana dengan jaringan yang sangat lambat? Anda mengklik tombol, permintaan 'MENDAPATKAN' membutuhkan waktu 10 detik untuk menerima jawabannya, tetapi dalam 5 detik menunggu Anda memutuskan untuk menutup tab. Itu akan menghancurkan komponen A menjadi sampah di lain waktu. Tunggu sebentar! , kami tidak berhenti berlangganan - sekarang 5 detik kemudian, respons muncul kembali dan logika dalam komponen yang hancur akan dieksekusi. Eksekusi itu sekarang dipertimbangkan out-of-contextdan dapat menghasilkan banyak hal termasuk kinerja yang sangat rendah.

Jadi, praktik terbaik adalah menggunakan takeUntil()dan berhenti berlangganan panggilan http saat komponen dihancurkan.

import { Component, OnInit, OnDestroy } from '@angular/core';
import { HttpClient } from '@angular/common/http';

import { Subject } from 'rxjs';
import { takeUntil } from 'rxjs/operators';

interface User {
  id: string;
  name: string;
  age: number;
}

@Component({
  selector: 'app-foobar',
  templateUrl: './foobar.component.html',
  styleUrls: ['./foobar.component.scss'],
})
export class FoobarComponent implements OnInit, OnDestroy {
  private user: User = null;
  private destroy$ = new Subject();

  constructor(private http: HttpClient) {}

  ngOnInit() {
    this.http
      .get<User>('api/user/id')
      .pipe(takeUntil(this.destroy$))
      .subscribe(user => {
        this.user = user;
      });
  }

  ngOnDestroy(): void {
    this.destroy$.next();  // trigger the unsubscribe
    this.destroy$.complete(); // finalize & clean up the subject stream
  }
}
un33k
sumber
Apakah saya tetap bisa menggunakan BehaviorSubject()bukannya dan menelepon hanya complete()di ngOnDestroy()?
Saksham
Anda masih harus berhenti berlangganan ... mengapa akan BehaviourSubjectberbeda?
Ben Taliadoros
Tidak perlu berhenti berlangganan dari HttpClient yang dapat diobservasi karena mereka dapat diobservasi hingga, yaitu mereka selesai setelah memancarkan nilai.
Yatharth Varshney
Anda harus berhenti berlangganan, anggap ada pencegat untuk bersulang kesalahan, tetapi Anda sudah meninggalkan halaman yang memicu kesalahan .. Anda akan melihat pesan kesalahan di halaman lain .... Juga pikirkan tentang halaman pemeriksaan kesehatan yang memanggil semua layanan microser ... dan seterusnya
Washington Guedes
12

Juga dengan modul HttpClient baru, perilaku tetap sama paket / common / http / src / jsonp.ts

Ricardo Martínez
sumber
Kode di atas tampaknya berasal dari unit test, menyiratkan bahwa dengan HttpClient, Anda harus memanggil .complete () sendiri ( github.com/angular/angular/blob/… )
CleverPatrick
Ya, Anda benar, tetapi jika Anda memeriksa ( github.com/angular/angular/blob/… ) Anda akan melihat hal yang sama. Mengenai pertanyaan utama, apakah perlu secara eksplisit berhenti berlangganan? sebagian besar kasus tidak, karena akan ditangani oleh Angular sendiri. Bisa jadi kasus di mana respons yang panjang dapat terjadi dan Anda telah pindah ke rute lain atau mungkin menghancurkan komponen, di mana Anda memiliki kemungkinan mencoba mengakses sesuatu yang tidak ada lagi menimbulkan pengecualian.
Ricardo Martínez
8

Anda tidak boleh berhenti berlangganan dari observable yang selesai secara otomatis (mis. Http, panggilan). Tapi itu perlu untuk berhenti berlangganan seperti yang dapat diamati seperti Observable.timer().

Mykhailo Mykhaliuk
sumber
6

Setelah beberapa saat pengujian, baca dokumentasi dan kode sumber HttpClient.

HttpClient: https://github.com/angular/angular/blob/master/packages/common/http/src/client.ts

HttpXhrBackend : https://github.com/angular/angular/blob/master/packages/common/http/src/xhr.ts

HttpClientModule: https://indepth.dev/exploring-the-httpclientmodule-in-angular/

Universitas Angular: https://blog.angular-university.io/angular-http/

Jenis Observable khusus ini adalah aliran bernilai tunggal: Jika permintaan HTTP berhasil, observable ini hanya akan memancarkan satu nilai dan kemudian menyelesaikan

Dan jawaban untuk seluruh Masalah "apakah saya MEMBUTUHKAN" untuk berhenti berlangganan?

Tergantung. Http call Memoryleaks tidak menjadi masalah. Masalahnya adalah logika dalam fungsi panggilan balik Anda.

Misalnya: Routing atau Login.

Jika panggilan Anda adalah panggilan masuk, Anda tidak perlu "berhenti berlangganan" tetapi Anda harus memastikan jika Pengguna meninggalkan halaman, Anda menangani respons dengan benar tanpa adanya pengguna.


this.authorisationService
      .authorize(data.username, data.password)
      .subscribe((res: HttpResponse<object>) => {
          this.handleLoginResponse(res);
        },
        (error: HttpErrorResponse) => {
          this.messageService.error('Authentication failed');
        },
        () => {
          this.messageService.info('Login has completed');
        })

Dari menjengkelkan menjadi berbahaya

Sekarang bayangkan saja, jaringan lebih lambat dari biasanya, panggilan membutuhkan waktu lebih lama 5 detik, dan pengguna meninggalkan tampilan login dan pergi ke "tampilan dukungan".

Komponen mungkin tidak aktif tetapi berlangganan. Jika ada respons, pengguna akan tiba-tiba dialihkan (tergantung implementasi handleResponse () Anda).

Ini bukan baik.

Bayangkan saja pengguna meninggalkan pc, percaya dia belum login. Tapi Anda masuk log logis pengguna, sekarang Anda memiliki masalah keamanan.

Apa yang dapat Anda lakukan TANPA berhenti berlangganan?

Membuat panggilan Anda tergantung pada kondisi tampilan saat ini:

  public isActive = false;
  public ngOnInit(): void {
    this.isActive = true;
  }

  public ngOnDestroy(): void {
    this.isActive = false;
  }

Pengguna .pipe(takeWhile(value => this.isActive))untuk memastikan respons hanya ditangani ketika tampilan aktif.


this.authorisationService
      .authorize(data.username, data.password).pipe(takeWhile(value => this.isActive))
      .subscribe((res: HttpResponse<object>) => {
          this.handleLoginResponse(res);
        },
        (error: HttpErrorResponse) => {
          this.messageService.error('Authentication failed');
        },
        () => {
          this.messageService.info('Login has completed');
        })

Tetapi bagaimana Anda bisa yakin bahwa langganan tidak menyebabkan kebocoran memori?

Anda dapat login jika "teardownLogic" diterapkan.

The teardownLogic dari suatu langganan akan dipanggil ketika subkripsi kosong atau berhenti berlangganan.


this.authorisationService
      .authorize(data.username, data.password).pipe(takeWhile(value => this.isActive))
      .subscribe((res: HttpResponse<object>) => {
          this.handleLoginResponse(res);
        },
        (error: HttpErrorResponse) => {
          this.messageService.error('Authentication failed');
        },
        () => {
          this.messageService.info('Login has completed');
    }).add(() => {
        // this is the teardown function
        // will be called in the end
      this.messageService.info('Teardown');
    });

Anda tidak harus berhenti berlangganan. Anda harus tahu jika ada masalah dalam logika Anda, yang dapat menyebabkan Masalah dalam langganan Anda. Dan urus mereka. Dalam kebanyakan kasus, itu tidak akan menjadi masalah, tetapi terutama pada tugas-tugas penting seperti autorisasi, Anda harus menjaga perilaku yang tidak terduga, apakah itu dengan "berhenti berlangganan" atau logika lain seperti pemipaan atau fungsi panggilan balik bersyarat.

mengapa tidak selalu berhenti berlangganan saja?

Bayangkan Anda membuat permintaan put atau postingan. Server menerima pesan dengan cara apa pun, hanya tanggapannya yang membutuhkan waktu. Berhenti berlangganan, tidak akan membatalkan pos atau menempatkan. Tetapi ketika Anda berhenti berlangganan, Anda tidak akan memiliki kesempatan untuk menangani respons atau menginformasikan Pengguna, misalnya melalui Dialog atau Toast / Pesan dll.

Wich menyebabkan Pengguna percaya, bahwa permintaan put / post tidak dilakukan.

Jadi itu tergantung. Ini adalah keputusan desain Anda, bagaimana menangani masalah seperti itu.

Masalah Luxus
sumber
4

Anda pasti harus membaca artikel ini . Ini menunjukkan kepada Anda mengapa Anda harus selalu berhenti berlangganan bahkan dari http .

Jika setelah membuat permintaan tetapi sebelum menerima jawaban dari back-end Anda menganggap komponen itu tidak perlu dan menghancurkannya, langganan Anda akan mempertahankan referensi ke komponen tersebut sehingga menciptakan peluang untuk menyebabkan kebocoran memori.

Memperbarui

Penegasan di atas tampaknya benar, tetapi bagaimanapun, ketika jawabannya kembali berlangganan http dihancurkan pula

MTZ
sumber
1
Ya dan Anda benar-benar akan melihat tanda 'batal' merah di tab jaringan peramban Anda sehingga Anda bisa memastikan itu berfungsi.
Simon_Weaver
-2

RxJS diamati pada dasarnya terkait dan bekerja sesuai dengan yang Anda berlangganan. Ketika kita membuat observable dan gerakan kita menyelesaikannya, observable secara otomatis akan ditutup dan berhenti berlangganan.

Mereka bekerja dengan cara yang sama dari para pengamat tetapi urutannya sangat berbeda. Praktik yang lebih baik untuk berhenti berlangganan mereka ketika komponen semakin hancur. Kami kemudian bisa melakukannya dengan misalnya. ini. $ manageSubscription.unsubscibe ()

Jika kita telah membuat sintaks yang dapat diamati seperti yang disebutkan di bawah ini seperti

** mengembalikan yang baru Dapat Diobservasi ((pengamat) => {** // Itu membuat diamati dalam keadaan dingin ** observer.complete () **}) **

Sachin Mishra
sumber